From jython-checkins at python.org Fri Feb 1 20:48:49 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 1 Feb 2013 20:48:49 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Upgrade_xerces_to_2=2E11=2E?= =?utf-8?q?0=2E_Also_correct_missing_guava_update_in_build=2Exml=2E?= Message-ID: <3YyTQT4rcszNHw@mail.python.org> http://hg.python.org/jython/rev/e919c10b49d6 changeset: 6967:e919c10b49d6 user: Frank Wierzbicki date: Fri Feb 01 11:48:33 2013 -0800 summary: Upgrade xerces to 2.11.0. Also correct missing guava update in build.xml. files: .idea/libraries/extlibs.xml | 3 ++- .idea/libraries/extlibs2.xml | 3 ++- b/.idea/libraries/extlibs.xml | 3 ++- build.xml | 5 +++-- extlibs/xercesImpl-2.11.0.jar | Bin extlibs/xercesImpl-2.9.1.jar | Bin extlibs/xml-apis-2.11.0.jar | Bin 7 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.idea/libraries/extlibs.xml b/.idea/libraries/extlibs.xml --- a/.idea/libraries/extlibs.xml +++ b/.idea/libraries/extlibs.xml @@ -20,7 +20,8 @@ - + + diff --git a/.idea/libraries/extlibs2.xml b/.idea/libraries/extlibs2.xml --- a/.idea/libraries/extlibs2.xml +++ b/.idea/libraries/extlibs2.xml @@ -23,7 +23,8 @@ - + + diff --git a/b/.idea/libraries/extlibs.xml b/b/.idea/libraries/extlibs.xml --- a/b/.idea/libraries/extlibs.xml +++ b/b/.idea/libraries/extlibs.xml @@ -23,7 +23,8 @@ - + + diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -642,7 +642,7 @@ - + @@ -667,7 +667,8 @@ - + + diff --git a/extlibs/xercesImpl-2.11.0.jar b/extlibs/xercesImpl-2.11.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..0aaa990f3ecadf60d28b5395dc87bbe49da0cdd7 GIT binary patch [stripped] diff --git a/extlibs/xercesImpl-2.9.1.jar b/extlibs/xercesImpl-2.9.1.jar deleted file mode 100644 Binary file extlibs/xercesImpl-2.9.1.jar has changed diff --git a/extlibs/xml-apis-2.11.0.jar b/extlibs/xml-apis-2.11.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..46733464fc746776c331ecc51061f3a05e662fd1 GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 1 21:16:10 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 1 Feb 2013 21:16:10 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_livetribe-jsr223_to_?= =?utf-8?b?Mi4wLjYu?= Message-ID: <3YyV221KrvzPjp@mail.python.org> http://hg.python.org/jython/rev/94d9942c7a13 changeset: 6968:94d9942c7a13 user: Frank Wierzbicki date: Fri Feb 01 12:16:00 2013 -0800 summary: Update livetribe-jsr223 to 2.0.6. files: .classpath | 2 +- .idea/libraries/extlibs.xml | 2 +- .idea/libraries/extlibs2.xml | 2 +- b/.idea/libraries/extlibs.xml | 2 +- build.xml | 2 +- extlibs/livetribe-jsr223-2.0.5.jar | Bin extlibs/livetribe-jsr223-2.0.6.jar | Bin 7 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.classpath b/.classpath --- a/.classpath +++ b/.classpath @@ -20,7 +20,7 @@ - + diff --git a/.idea/libraries/extlibs.xml b/.idea/libraries/extlibs.xml --- a/.idea/libraries/extlibs.xml +++ b/.idea/libraries/extlibs.xml @@ -6,7 +6,7 @@ - + diff --git a/.idea/libraries/extlibs2.xml b/.idea/libraries/extlibs2.xml --- a/.idea/libraries/extlibs2.xml +++ b/.idea/libraries/extlibs2.xml @@ -7,7 +7,7 @@ - + diff --git a/b/.idea/libraries/extlibs.xml b/b/.idea/libraries/extlibs.xml --- a/b/.idea/libraries/extlibs.xml +++ b/b/.idea/libraries/extlibs.xml @@ -6,7 +6,7 @@ - + diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -192,7 +192,7 @@ - + diff --git a/extlibs/livetribe-jsr223-2.0.5.jar b/extlibs/livetribe-jsr223-2.0.5.jar deleted file mode 100644 Binary file extlibs/livetribe-jsr223-2.0.5.jar has changed diff --git a/extlibs/livetribe-jsr223-2.0.6.jar b/extlibs/livetribe-jsr223-2.0.6.jar new file mode 100644 index 0000000000000000000000000000000000000000..b35c0977f409090ccb831226a426403e11410e54 GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 1 21:52:50 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 1 Feb 2013 21:52:50 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_to_stringtemplate-3?= =?utf-8?b?LjIuMS5qYXIu?= Message-ID: <3YyVrL22F4zSQM@mail.python.org> http://hg.python.org/jython/rev/3f6a8baa34b5 changeset: 6969:3f6a8baa34b5 user: Frank Wierzbicki date: Fri Feb 01 12:52:42 2013 -0800 summary: Update to stringtemplate-3.2.1.jar. files: .idea/libraries/extlibs.xml | 2 +- .idea/libraries/extlibs2.xml | 2 +- b/.idea/libraries/extlibs.xml | 2 +- build.xml | 2 +- extlibs/stringtemplate-3.2.jar | Bin 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.idea/libraries/extlibs.xml b/.idea/libraries/extlibs.xml --- a/.idea/libraries/extlibs.xml +++ b/.idea/libraries/extlibs.xml @@ -3,7 +3,7 @@ - + diff --git a/.idea/libraries/extlibs2.xml b/.idea/libraries/extlibs2.xml --- a/.idea/libraries/extlibs2.xml +++ b/.idea/libraries/extlibs2.xml @@ -4,7 +4,7 @@ - + diff --git a/b/.idea/libraries/extlibs.xml b/b/.idea/libraries/extlibs.xml --- a/b/.idea/libraries/extlibs.xml +++ b/b/.idea/libraries/extlibs.xml @@ -3,7 +3,7 @@ - + diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -191,7 +191,7 @@ - + diff --git a/extlibs/stringtemplate-3.2.jar b/extlibs/stringtemplate-3.2.jar deleted file mode 100644 Binary file extlibs/stringtemplate-3.2.jar has changed -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 1 22:24:06 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 1 Feb 2013 22:24:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Oops=2C_actually_add_string?= =?utf-8?b?dGVtcGxhdGUtMy4yLjEu?= Message-ID: <3YyWXQ1KxwzSRc@mail.python.org> http://hg.python.org/jython/rev/972d0e4561d1 changeset: 6970:972d0e4561d1 user: Frank Wierzbicki date: Fri Feb 01 13:23:58 2013 -0800 summary: Oops, actually add stringtemplate-3.2.1. files: extlibs/stringtemplate-3.2.1.jar | Bin 1 files changed, 0 insertions(+), 0 deletions(-) diff --git a/extlibs/stringtemplate-3.2.1.jar b/extlibs/stringtemplate-3.2.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..d0e11b7193734c5b2784127951a1a91372f2ab94 GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 1 22:44:03 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 1 Feb 2013 22:44:03 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_to_jnr-constants-0?= =?utf-8?b?LjguNC5qYXI=?= Message-ID: <3YyWzR275fzSPm@mail.python.org> http://hg.python.org/jython/rev/b8a666b69455 changeset: 6971:b8a666b69455 user: Frank Wierzbicki date: Fri Feb 01 13:43:53 2013 -0800 summary: Update to jnr-constants-0.8.4.jar files: .classpath | 2 +- build.xml | 4 ++-- extlibs/jnr-constants-0.8.3-SNAPSHOT.jar | Bin extlibs/jnr-constants-0.8.4.jar | Bin 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.classpath b/.classpath --- a/.classpath +++ b/.classpath @@ -37,7 +37,7 @@ - + diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -221,7 +221,7 @@ - + @@ -666,7 +666,7 @@ - + diff --git a/extlibs/jnr-constants-0.8.3-SNAPSHOT.jar b/extlibs/jnr-constants-0.8.3-SNAPSHOT.jar deleted file mode 100644 Binary file extlibs/jnr-constants-0.8.3-SNAPSHOT.jar has changed diff --git a/extlibs/jnr-constants-0.8.4.jar b/extlibs/jnr-constants-0.8.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..f0ff6bb1a1796560c09121b4366a5d04032156af GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 1 23:18:40 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 1 Feb 2013 23:18:40 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Upgrade_to_jffi-1=2E2=2E6?= =?utf-8?q?=2E?= Message-ID: <3YyXlN1x7dzST6@mail.python.org> http://hg.python.org/jython/rev/d6efc161f026 changeset: 6972:d6efc161f026 user: Frank Wierzbicki date: Fri Feb 01 14:18:33 2013 -0800 summary: Upgrade to jffi-1.2.6. files: .classpath | 2 +- build.xml | 4 ++-- extlibs/jffi-1.2.2-SNAPSHOT.jar | Bin 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.classpath b/.classpath --- a/.classpath +++ b/.classpath @@ -36,7 +36,7 @@ - + diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -217,7 +217,7 @@ - + @@ -662,7 +662,7 @@ - + diff --git a/extlibs/jffi-1.2.2-SNAPSHOT.jar b/extlibs/jffi-1.2.2-SNAPSHOT.jar deleted file mode 100644 Binary file extlibs/jffi-1.2.2-SNAPSHOT.jar has changed -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 1 23:32:07 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 1 Feb 2013 23:32:07 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_all_jffi_jars_-_base?= =?utf-8?q?d_on_latest_from_JRuby=2E?= Message-ID: <3YyY2v6zX7zNBG@mail.python.org> http://hg.python.org/jython/rev/aedad34262f9 changeset: 6973:aedad34262f9 user: Frank Wierzbicki date: Fri Feb 01 14:31:58 2013 -0800 summary: Update all jffi jars - based on latest from JRuby. files: extlibs/jffi-1.2.6.jar | Bin extlibs/jffi-Darwin.jar | Bin extlibs/jffi-arm-Linux.jar | Bin extlibs/jffi-i386-Linux.jar | Bin extlibs/jffi-i386-SunOS.jar | Bin extlibs/jffi-i386-Windows.jar | Bin extlibs/jffi-sparcv9-SunOS.jar | Bin extlibs/jffi-x86_64-FreeBSD.jar | Bin extlibs/jffi-x86_64-Linux.jar | Bin extlibs/jffi-x86_64-SunOS.jar | Bin extlibs/jffi-x86_64-Windows.jar | Bin 11 files changed, 0 insertions(+), 0 deletions(-) diff --git a/extlibs/jffi-1.2.6.jar b/extlibs/jffi-1.2.6.jar new file mode 100644 index 0000000000000000000000000000000000000000..a6b2be6c39798f0fc5958aa309924506b74f0f9f GIT binary patch [stripped] diff --git a/extlibs/jffi-Darwin.jar b/extlibs/jffi-Darwin.jar index 842abe4f47ef6b45fb7d2c1702500787bb638c77..009191ac8c0dcaf63267ea0103e73ea2a9895e43 GIT binary patch [stripped] diff --git a/extlibs/jffi-arm-Linux.jar b/extlibs/jffi-arm-Linux.jar new file mode 100644 index 0000000000000000000000000000000000000000..b3c5d977c1b7ad3fc196bfe2cd24fe56129dc8f9 GIT binary patch [stripped] diff --git a/extlibs/jffi-i386-Linux.jar b/extlibs/jffi-i386-Linux.jar index 647fc394a661a4ed35875b72ff91d323ef8c7ce9..7e23b400cce5e3b4861141ee4f10730fbcbdb5de GIT binary patch [stripped] diff --git a/extlibs/jffi-i386-SunOS.jar b/extlibs/jffi-i386-SunOS.jar index 42397e1d627634fd247cdd81f0363466eb3ae595..68bcacc39ac49a01929be37ec67c6784612bc966 GIT binary patch [stripped] diff --git a/extlibs/jffi-i386-Windows.jar b/extlibs/jffi-i386-Windows.jar index bd421473102609ceb266ee12456b5462bc317f91..90a6baecf76870212dc4f8cb7aa56e0962d1f858 GIT binary patch [stripped] diff --git a/extlibs/jffi-sparcv9-SunOS.jar b/extlibs/jffi-sparcv9-SunOS.jar index 8235a3484884243b1d17d5e19340e4166f3b0b55..306788c0810baeccb93f191316844d5d0d9e6254 GIT binary patch [stripped] diff --git a/extlibs/jffi-x86_64-FreeBSD.jar b/extlibs/jffi-x86_64-FreeBSD.jar index 8235a3484884243b1d17d5e19340e4166f3b0b55..32824a7e209b57e06808d4de65de4443225a5349 GIT binary patch [stripped] diff --git a/extlibs/jffi-x86_64-Linux.jar b/extlibs/jffi-x86_64-Linux.jar index 6e5ed6ddf10176537b22fe0d10edbf42a78c815b..99deb014d458c5a42b5290832b8141acf42212dd GIT binary patch [stripped] diff --git a/extlibs/jffi-x86_64-SunOS.jar b/extlibs/jffi-x86_64-SunOS.jar index 8235a3484884243b1d17d5e19340e4166f3b0b55..b8240422d442b4bcf0e95d6b6a0a02714aeac827 GIT binary patch [stripped] diff --git a/extlibs/jffi-x86_64-Windows.jar b/extlibs/jffi-x86_64-Windows.jar index 6d221233b65387c1ca9470619136204e93a60855..667e48db8193809b21118cff14b23184d558821a GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 2 01:02:56 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 2 Feb 2013 01:02:56 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_to_jnr-ffi-0=2E7=2E1?= =?utf-8?b?MC5qYXIsIGpuci1uZXRkYi0xLjEuMS5qYXIsIGpuci1wb3NpeC0yLjQuMC5q?= =?utf-8?b?YXIu?= Message-ID: <3Yyb3h0ypVzPrk@mail.python.org> http://hg.python.org/jython/rev/a113ab4287fc changeset: 6974:a113ab4287fc user: Frank Wierzbicki date: Fri Feb 01 16:02:44 2013 -0800 summary: Update to jnr-ffi-0.7.10.jar, jnr-netdb-1.1.1.jar, jnr-posix-2.4.0.jar. files: .classpath | 6 +++--- build.xml | 14 ++++++++------ extlibs/jnr-ffi-0.7.10.jar | Bin extlibs/jnr-ffi-0.7.4-SNAPSHOT.jar | Bin extlibs/jnr-netdb-1.0.6-SNAPSHOT.jar | Bin extlibs/jnr-netdb-1.1.1.jar | Bin extlibs/jnr-posix-2.1-SNAPSHOT.jar | Bin extlibs/jnr-posix-2.4.0.jar | Bin 8 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.classpath b/.classpath --- a/.classpath +++ b/.classpath @@ -38,9 +38,9 @@ - - - + + + diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -200,6 +200,7 @@ + @@ -218,9 +219,9 @@ - - - + + + @@ -645,6 +646,7 @@ + @@ -663,9 +665,9 @@ - - - + + + diff --git a/extlibs/jnr-ffi-0.7.10.jar b/extlibs/jnr-ffi-0.7.10.jar new file mode 100644 index 0000000000000000000000000000000000000000..9e2e64f276334bed30c62d12823d5088b5672425 GIT binary patch [stripped] diff --git a/extlibs/jnr-ffi-0.7.4-SNAPSHOT.jar b/extlibs/jnr-ffi-0.7.4-SNAPSHOT.jar deleted file mode 100644 Binary file extlibs/jnr-ffi-0.7.4-SNAPSHOT.jar has changed diff --git a/extlibs/jnr-netdb-1.0.6-SNAPSHOT.jar b/extlibs/jnr-netdb-1.0.6-SNAPSHOT.jar deleted file mode 100644 Binary file extlibs/jnr-netdb-1.0.6-SNAPSHOT.jar has changed diff --git a/extlibs/jnr-netdb-1.1.1.jar b/extlibs/jnr-netdb-1.1.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..731f6ec0fdc5c420ec55edec6c28092b06c70e32 GIT binary patch [stripped] diff --git a/extlibs/jnr-posix-2.1-SNAPSHOT.jar b/extlibs/jnr-posix-2.1-SNAPSHOT.jar deleted file mode 100644 Binary file extlibs/jnr-posix-2.1-SNAPSHOT.jar has changed diff --git a/extlibs/jnr-posix-2.4.0.jar b/extlibs/jnr-posix-2.4.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..36f3fc1d217091941a1079e9ebccc70094a5ede6 GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 2 11:57:18 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 2 Feb 2013 11:57:18 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_for_=231997=3A_Incomple?= =?utf-8?q?te_ssl_support_in_Jython2=2E7a2?= Message-ID: <3YysZk6GJtzQ0F@mail.python.org> http://hg.python.org/jython/rev/fe47bfd36e5c changeset: 6975:fe47bfd36e5c user: Alan Kennedy date: Sat Feb 02 10:55:45 2013 +0000 summary: Fix for #1997: Incomplete ssl support in Jython2.7a2 files: Lib/socket.py | 35 ++++++++++++------------ Lib/ssl.py | 2 +- Lib/test/test_socket_ssl.py | 9 ++++++ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -1850,24 +1850,31 @@ class ssl: - def __init__(self, plain_sock, keyfile=None, certfile=None): + def __init__(self, jython_socket_wrapper, keyfile=None, certfile=None): try: - self.ssl_sock = self._make_ssl_socket(plain_sock) - self._in_buf = java.io.BufferedInputStream(self.ssl_sock.getInputStream()) - self._out_buf = java.io.BufferedOutputStream(self.ssl_sock.getOutputStream()) + self.jython_socket_wrapper = jython_socket_wrapper + jython_socket = self.jython_socket_wrapper._sock + self.java_ssl_socket = self._make_ssl_socket(jython_socket) + self._in_buf = java.io.BufferedInputStream(self.java_ssl_socket.getInputStream()) + self._out_buf = java.io.BufferedOutputStream(self.java_ssl_socket.getOutputStream()) except java.lang.Exception, jlx: raise _map_exception(jlx) - def _make_ssl_socket(self, plain_socket, auto_close=0): - java_net_socket = plain_socket._get_jsocket() + def _make_ssl_socket(self, jython_socket, auto_close=0): + java_net_socket = jython_socket._get_jsocket() assert isinstance(java_net_socket, java.net.Socket) host = java_net_socket.getInetAddress().getHostAddress() port = java_net_socket.getPort() factory = javax.net.ssl.SSLSocketFactory.getDefault(); - ssl_socket = factory.createSocket(java_net_socket, host, port, auto_close) - ssl_socket.setEnabledCipherSuites(ssl_socket.getSupportedCipherSuites()) - ssl_socket.startHandshake() - return ssl_socket + java_ssl_socket = factory.createSocket(java_net_socket, host, port, auto_close) + java_ssl_socket.setEnabledCipherSuites(java_ssl_socket.getSupportedCipherSuites()) + java_ssl_socket.startHandshake() + return java_ssl_socket + + def __getattr__(self, attr_name): + if hasattr(self.jython_socket_wrapper, attr_name): + return getattr(self.jython_socket_wrapper, attr_name) + raise AttributeError(attr_name) def read(self, n=4096): try: @@ -1891,7 +1898,7 @@ def _get_server_cert(self): try: - return self.ssl_sock.getSession().getPeerCertificates()[0] + return self.java_ssl_socket.getSession().getPeerCertificates()[0] except java.lang.Exception, jlx: raise _map_exception(jlx) @@ -1903,12 +1910,6 @@ cert = self._get_server_cert() return cert.getIssuerDN().toString() -_realssl = ssl -def ssl(sock, keyfile=None, certfile=None): - if hasattr(sock, "_sock"): - sock = sock._sock - return _realssl(sock, keyfile, certfile) - def test(): s = socket(AF_INET, SOCK_STREAM) s.connect(("", 80)) diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -7,4 +7,4 @@ import socket -wrap = socket.ssl +wrap_socket = socket.ssl diff --git a/Lib/test/test_socket_ssl.py b/Lib/test/test_socket_ssl.py --- a/Lib/test/test_socket_ssl.py +++ b/Lib/test/test_socket_ssl.py @@ -61,11 +61,20 @@ time.sleep(1) connector() +def test_https_socket(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(('www.verisign.com', 443)) + ssl_sock = socket.ssl(s) + ssl_sock.server() + ssl_sock.issuer() + s.close() + def test_main(): if not hasattr(socket, "ssl"): raise test_support.TestSkipped("socket module has no ssl support") test_rude_shutdown() test_basic() + test_https_socket() if __name__ == "__main__": test_main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 2 13:34:49 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 2 Feb 2013 13:34:49 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Misc_tidyups_to_make_urllib?= =?utf-8?q?2_tests_pass?= Message-ID: <3YyvlF6kkNzSMw@mail.python.org> http://hg.python.org/jython/rev/fd6e694b4a70 changeset: 6976:fd6e694b4a70 user: Alan Kennedy date: Sat Feb 02 12:33:21 2013 +0000 summary: Misc tidyups to make urllib2 tests pass files: Lib/socket.py | 8 ++++++++ Lib/test/test_urllib2.py | 6 +++--- Lib/urllib.py | 8 +++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -700,6 +700,14 @@ except java.lang.Exception, jlx: raise _map_exception(jlx) +# +# Skeleton implementation of gethostbyname_ex +# Needed because urllib2 refers to it +# + +def gethostbyname_ex(name): + return (name, [], gethostbyname(name)) + def gethostbyaddr(name): names, addrs = _gethostbyaddr(name) return (names[0], names, addrs) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -307,6 +307,8 @@ def getresponse(self): return MockHTTPResponse(MockFile(), {}, 200, "OK") + def close(self): pass + class MockHandler: # useful for testing handler machinery # see add_ordered_mock_handlers() docstring @@ -601,7 +603,7 @@ class HandlerTests(unittest.TestCase): - @unittest.skip("FIXME: broken") + @unittest.skipIf(test_support.is_jython, "Required SSL support not yet available on jython") def test_ftp(self): class MockFTPWrapper: def __init__(self, data): self.data = data @@ -753,7 +755,6 @@ self.assertEqual(req.type, "ftp") self.assertEqual(req.type == "ftp", ftp) - @unittest.skip("FIXME: broken") def test_http(self): h = urllib2.AbstractHTTPHandler() @@ -842,7 +843,6 @@ p_ds_req = h.do_request_(ds_req) self.assertEqual(p_ds_req.unredirected_hdrs["Host"],"example.com") - @unittest.skip("FIXME: broken") def test_fixpath_in_weirdurls(self): # Issue4493: urllib2 to supply '/' when to urls where path does not # start with'/' diff --git a/Lib/urllib.py b/Lib/urllib.py --- a/Lib/urllib.py +++ b/Lib/urllib.py @@ -1072,7 +1072,13 @@ _hostprog = re.compile('^//([^/?]*)(.*)$') match = _hostprog.match(url) - if match: return match.group(1, 2) + # http://bugs.python.org/issue4493 + if match: + host_port = match.group(1) + path = match.group(2) + if path and not path.startswith('/'): + path = '/' + path + return host_port, path return None, url _userprog = None -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 2 14:54:28 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 2 Feb 2013 14:54:28 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Import_latest_urllib=2Epy_f?= =?utf-8?q?rom_cpython_2=2E7=3A?= Message-ID: <3YyxW86FMszSMF@mail.python.org> http://hg.python.org/jython/rev/52f8fb53c414 changeset: 6977:52f8fb53c414 user: Alan Kennedy date: Sat Feb 02 13:46:56 2013 +0000 summary: Import latest urllib.py from cpython 2.7: http://hg.python.org/cpython/file/b6b707063991/Lib/urllib.py files: Lib/urllib.py | 226 ++++++++++++++----------------------- 1 files changed, 89 insertions(+), 137 deletions(-) diff --git a/Lib/urllib.py b/Lib/urllib.py --- a/Lib/urllib.py +++ b/Lib/urllib.py @@ -27,6 +27,8 @@ import os import time import sys +import base64 + from urlparse import urljoin as basejoin __all__ = ["urlopen", "URLopener", "FancyURLopener", "urlretrieve", @@ -42,9 +44,7 @@ MAXFTPCACHE = 10 # Trim the ftp cache beyond this size # Helper for non-unix systems -if os.name == 'mac': - from macurl2path import url2pathname, pathname2url -elif (os._name if sys.platform.startswith('java') else os.name) == 'nt': +if os.name == 'nt': from nturl2path import url2pathname, pathname2url elif os.name == 'riscos': from rourl2path import url2pathname, pathname2url @@ -94,7 +94,7 @@ def urlcleanup(): if _urlopener: _urlopener.cleanup() - _safemaps.clear() + _safe_quoters.clear() ftpcache.clear() # check for SSL @@ -177,8 +177,8 @@ def open(self, fullurl, data=None): """Use URLopener().open(file) instead of open(file, 'r').""" fullurl = unwrap(toBytes(fullurl)) - # percent encode url. fixing lame server errors like space within url - # parts + # percent encode url, fixing lame server errors for e.g, like space + # within url paths. fullurl = quote(fullurl, safe="%/:=&?~#+!$,;'@()*[]|") if self.tempcache and fullurl in self.tempcache: filename, headers = self.tempcache[fullurl] @@ -232,9 +232,9 @@ try: fp = self.open_local_file(url1) hdrs = fp.info() - del fp + fp.close() return url2pathname(splithost(url1)[1]), hdrs - except IOError, msg: + except IOError: pass fp = self.open(url, data) try: @@ -259,9 +259,9 @@ size = -1 read = 0 blocknum = 0 + if "content-length" in headers: + size = int(headers["Content-Length"]) if reporthook: - if "content-length" in headers: - size = int(headers["Content-Length"]) reporthook(blocknum, bs, size) while 1: block = fp.read(bs) @@ -276,8 +276,6 @@ tfp.close() finally: fp.close() - del fp - del tfp # raise exception if actual size does not match content-length header if size >= 0 and read < size: @@ -322,13 +320,13 @@ if not host: raise IOError, ('http error', 'no host given') if proxy_passwd: - import base64 + proxy_passwd = unquote(proxy_passwd) proxy_auth = base64.b64encode(proxy_passwd).strip() else: proxy_auth = None if user_passwd: - import base64 + user_passwd = unquote(user_passwd) auth = base64.b64encode(user_passwd).strip() else: auth = None @@ -343,9 +341,7 @@ if auth: h.putheader('Authorization', 'Basic %s' % auth) if realhost: h.putheader('Host', realhost) for args in self.addheaders: h.putheader(*args) - h.endheaders() - if data is not None: - h.send(data) + h.endheaders(data) errcode, errmsg, headers = h.getreply() fp = h.getfile() if errcode == -1: @@ -380,7 +376,6 @@ def http_error_default(self, url, fp, errcode, errmsg, headers): """Default error handler: close the connection and raise IOError.""" - void = fp.read() fp.close() raise IOError, ('http error', errcode, errmsg, headers) @@ -415,12 +410,12 @@ #print "proxy via https:", host, selector if not host: raise IOError, ('https error', 'no host given') if proxy_passwd: - import base64 + proxy_passwd = unquote(proxy_passwd) proxy_auth = base64.b64encode(proxy_passwd).strip() else: proxy_auth = None if user_passwd: - import base64 + user_passwd = unquote(user_passwd) auth = base64.b64encode(user_passwd).strip() else: auth = None @@ -438,9 +433,7 @@ if auth: h.putheader('Authorization', 'Basic %s' % auth) if realhost: h.putheader('Host', realhost) for args in self.addheaders: h.putheader(*args) - h.endheaders() - if data is not None: - h.send(data) + h.endheaders(data) errcode, errmsg, headers = h.getreply() fp = h.getfile() if errcode == -1: @@ -491,6 +484,8 @@ urlfile = file if file[:1] == '/': urlfile = 'file://' + file + elif file[:2] == './': + raise ValueError("local file url may start with / or file:. Unknown url of type: %s" % url) return addinfourl(open(localname, 'rb'), headers, urlfile) host, port = splitport(host) @@ -519,8 +514,8 @@ if user: user, passwd = splitpasswd(user) else: passwd = None host = unquote(host) - user = unquote(user or '') - passwd = unquote(passwd or '') + user = user or '' + passwd = passwd or '' host = socket.gethostbyname(host) if not port: import ftplib @@ -598,7 +593,6 @@ time.gmtime(time.time()))) msg.append('Content-type: %s' % type) if encoding == 'base64': - import base64 data = base64.decodestring(data) else: data = unquote(data) @@ -648,7 +642,6 @@ newurl = headers['uri'] else: return - void = fp.read() fp.close() # In case the server sent a relative URL, join with original: newurl = basejoin(self.type + ":" + url, newurl) @@ -785,7 +778,7 @@ else: return self.open(newurl, data) - def get_user_passwd(self, host, realm, clear_cache = 0): + def get_user_passwd(self, host, realm, clear_cache=0): key = realm + '@' + host.lower() if key in self.auth_cache: if clear_cache: @@ -858,13 +851,16 @@ """Class used by open_ftp() for cache of open FTP connections.""" def __init__(self, user, passwd, host, port, dirs, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + persistent=True): self.user = user self.passwd = passwd self.host = host self.port = port self.dirs = dirs self.timeout = timeout + self.refcount = 0 + self.keepalive = persistent self.init() def init(self): @@ -891,7 +887,7 @@ # Try to retrieve as a file try: cmd = 'RETR ' + file - conn = self.ftp.ntransfercmd(cmd) + conn, retrlen = self.ftp.ntransfercmd(cmd) except ftplib.error_perm, reason: if str(reason)[:3] != '550': raise IOError, ('ftp error', reason), sys.exc_info()[2] @@ -911,11 +907,14 @@ cmd = 'LIST ' + file else: cmd = 'LIST' - conn = self.ftp.ntransfercmd(cmd) + conn, retrlen = self.ftp.ntransfercmd(cmd) self.busy = 1 + ftpobj = addclosehook(conn.makefile('rb'), self.file_close) + self.refcount += 1 + conn.close() # Pass back both a suitably decorated object and a retrieval length - return (addclosehook(conn[0].makefile('rb'), - self.endtransfer), conn[1]) + return (ftpobj, retrlen) + def endtransfer(self): if not self.busy: return @@ -926,6 +925,17 @@ pass def close(self): + self.keepalive = False + if self.refcount <= 0: + self.real_close() + + def file_close(self): + self.endtransfer() + self.refcount -= 1 + if self.refcount <= 0 and not self.keepalive: + self.real_close() + + def real_close(self): self.endtransfer() try: self.ftp.close() @@ -970,11 +980,11 @@ self.hookargs = hookargs def close(self): - addbase.close(self) if self.closehook: self.closehook(*self.hookargs) self.closehook = None self.hookargs = None + addbase.close(self) class addinfo(addbase): """class to add an info() method to an open file.""" @@ -1072,7 +1082,6 @@ _hostprog = re.compile('^//([^/?]*)(.*)$') match = _hostprog.match(url) - # http://bugs.python.org/issue4493 if match: host_port = match.group(1) path = match.group(2) @@ -1090,7 +1099,7 @@ _userprog = re.compile('^(.*)@(.*)$') match = _userprog.match(host) - if match: return map(unquote, match.group(1, 2)) + if match: return match.group(1, 2) return None, host _passwdprog = None @@ -1099,7 +1108,7 @@ global _passwdprog if _passwdprog is None: import re - _passwdprog = re.compile('^([^:]*):(.*)$') + _passwdprog = re.compile('^([^:]*):(.*)$',re.S) match = _passwdprog.match(user) if match: return match.group(1, 2) @@ -1182,21 +1191,29 @@ if match: return match.group(1, 2) return attr, None +# urlparse contains a duplicate of this method to avoid a circular import. If +# you update this method, also update the copy in urlparse. This code +# duplication does not exist in Python3. + _hexdig = '0123456789ABCDEFabcdef' -_hextochr = dict((a+b, chr(int(a+b,16))) for a in _hexdig for b in _hexdig) +_hextochr = dict((a + b, chr(int(a + b, 16))) + for a in _hexdig for b in _hexdig) def unquote(s): """unquote('abc%20def') -> 'abc def'.""" res = s.split('%') - for i in xrange(1, len(res)): - item = res[i] + # fastpath + if len(res) == 1: + return s + s = res[0] + for item in res[1:]: try: - res[i] = _hextochr[item[:2]] + item[2:] + s += _hextochr[item[:2]] + item[2:] except KeyError: - res[i] = '%' + item + s += '%' + item except UnicodeDecodeError: - res[i] = unichr(int(item[:2], 16)) + item[2:] - return "".join(res) + s += unichr(int(item[:2], 16)) + item[2:] + return s def unquote_plus(s): """unquote('%7e/abc+def') -> '~/abc def'""" @@ -1206,9 +1223,12 @@ always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' '0123456789' '_.-') -_safemaps = {} +_safe_map = {} +for i, c in zip(xrange(256), str(bytearray(xrange(256)))): + _safe_map[c] = c if (i < 128 and c in always_safe) else '%{:02X}'.format(i) +_safe_quoters = {} -def quote(s, safe = '/'): +def quote(s, safe='/'): """quote('abc def') -> 'abc%20def' Each part of a URL, e.g. the path info, the query, etc., has a @@ -1229,27 +1249,32 @@ called on a path where the existing slash characters are used as reserved characters. """ + # fastpath + if not s: + if s is None: + raise TypeError('None object cannot be quoted') + return s cachekey = (safe, always_safe) try: - safe_map = _safemaps[cachekey] + (quoter, safe) = _safe_quoters[cachekey] except KeyError: - safe += always_safe - safe_map = {} - for i in range(256): - c = chr(i) - safe_map[c] = (c in safe) and c or ('%%%02X' % i) - _safemaps[cachekey] = safe_map - res = map(safe_map.__getitem__, s) - return ''.join(res) + safe_map = _safe_map.copy() + safe_map.update([(c, c) for c in safe]) + quoter = safe_map.__getitem__ + safe = always_safe + safe + _safe_quoters[cachekey] = (quoter, safe) + if not s.rstrip(safe): + return s + return ''.join(map(quoter, s)) -def quote_plus(s, safe = ''): +def quote_plus(s, safe=''): """Quote the query fragment of a URL; replacing ' ' with '+'""" if ' ' in s: s = quote(s, safe + ' ') return s.replace(' ', '+') return quote(s, safe) -def urlencode(query,doseq=0): +def urlencode(query, doseq=0): """Encode a sequence of two-element tuples or dictionary into a URL query string. If any values in the query arg are sequences and doseq is true, each @@ -1301,7 +1326,7 @@ else: try: # is this a sufficient test for sequence-ness? - x = len(v) + len(v) except TypeError: # not a sequence v = quote_plus(str(v)) @@ -1342,7 +1367,8 @@ # strip port off host hostonly, port = splitport(host) # check if the host ends with any of the DNS suffixes - for name in no_proxy.split(','): + no_proxy_list = [proxy.strip() for proxy in no_proxy.split(',')] + for name in no_proxy_list: if name and (hostonly.endswith(name) or host.endswith(name)): return 1 # otherwise, don't bypass @@ -1401,7 +1427,7 @@ else: mask = int(mask[1:]) - mask = 32 - mask + mask = 32 - mask if (hostIP >> mask) == (base >> mask): return True @@ -1411,7 +1437,6 @@ return False - def getproxies_macosx_sysconf(): """Return a dictionary of scheme -> proxy server URL mappings. @@ -1420,8 +1445,6 @@ """ return _get_proxies() - - def proxy_bypass(host): if getproxies_environment(): return proxy_bypass_environment(host) @@ -1525,18 +1548,11 @@ # '' string by the localhost entry and the corresponding # canonical entry. proxyOverride = proxyOverride.split(';') - i = 0 - while i < len(proxyOverride): - if proxyOverride[i] == '': - proxyOverride[i:i+1] = ['localhost', - '127.0.0.1', - socket.gethostname(), - socket.gethostbyname( - socket.gethostname())] - i += 1 - # print proxyOverride # now check if we match one of the registry values. for test in proxyOverride: + if test == '': + if '.' not in rawHost: + return 1 test = test.replace(".", r"\.") # mask dots test = test.replace("*", r".*") # change glob sequence test = test.replace("?", r".") # change glob char @@ -1584,67 +1600,3 @@ # Report during remote transfers print "Block number: %d, Block size: %d, Total size: %d" % ( blocknum, blocksize, totalsize) - -# Test program -def test(args=[]): - if not args: - args = [ - '/etc/passwd', - 'file:/etc/passwd', - 'file://localhost/etc/passwd', - 'ftp://ftp.gnu.org/pub/README', - 'http://www.python.org/index.html', - ] - if hasattr(URLopener, "open_https"): - args.append('https://synergy.as.cmu.edu/~geek/') - try: - for url in args: - print '-'*10, url, '-'*10 - fn, h = urlretrieve(url, None, reporthook) - print fn - if h: - print '======' - for k in h.keys(): print k + ':', h[k] - print '======' - fp = open(fn, 'rb') - data = fp.read() - del fp - if '\r' in data: - table = string.maketrans("", "") - data = data.translate(table, "\r") - print data - fn, h = None, None - print '-'*40 - finally: - urlcleanup() - -def main(): - import getopt, sys - try: - opts, args = getopt.getopt(sys.argv[1:], "th") - except getopt.error, msg: - print msg - print "Use -h for help" - return - t = 0 - for o, a in opts: - if o == '-t': - t = t + 1 - if o == '-h': - print "Usage: python urllib.py [-t] [url ...]" - print "-t runs self-test;", - print "otherwise, contents of urls are printed" - return - if t: - if t > 1: - test1() - test(args) - else: - if not args: - print "Use -h for help" - for url in args: - print urlopen(url).read(), - -# Run test program when run as a script -if __name__ == '__main__': - main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 2 14:54:30 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 2 Feb 2013 14:54:30 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Reapply_Jython_adjustments?= =?utf-8?q?=2E?= Message-ID: <3YyxWB1WHSzSRK@mail.python.org> http://hg.python.org/jython/rev/32e4e1d648e5 changeset: 6978:32e4e1d648e5 user: Alan Kennedy date: Sat Feb 02 13:52:05 2013 +0000 summary: Reapply Jython adjustments. files: Lib/urllib.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/urllib.py b/Lib/urllib.py --- a/Lib/urllib.py +++ b/Lib/urllib.py @@ -44,7 +44,7 @@ MAXFTPCACHE = 10 # Trim the ftp cache beyond this size # Helper for non-unix systems -if os.name == 'nt': +if (os._name if sys.platform.startswith('java') else os.name) == 'nt': from nturl2path import url2pathname, pathname2url elif os.name == 'riscos': from rourl2path import url2pathname, pathname2url -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 2 16:20:10 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 2 Feb 2013 16:20:10 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Import_latest_test=5Furllib?= =?utf-8?q?=2C_test=5Furllib2_and_test=5Furllib2=5Flocalnet_from_cpython?= Message-ID: <3YyzQ23M9yzSLf@mail.python.org> http://hg.python.org/jython/rev/a720ee1162d6 changeset: 6979:a720ee1162d6 user: Alan Kennedy date: Sat Feb 02 14:18:15 2013 +0000 summary: Import latest test_urllib, test_urllib2 and test_urllib2_localnet from cpython 2.7: http://hg.python.org/cpython/file/b6b707063991/ files: Lib/test/test_urllib.py | 253 +++++++++++-- Lib/test/test_urllib2.py | 99 ++++- Lib/test/test_urllib2_localnet.py | 331 +++++++++++++++-- 3 files changed, 590 insertions(+), 93 deletions(-) diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -3,12 +3,16 @@ import urllib import httplib import unittest -from test import test_support import os +import sys import mimetools import tempfile import StringIO +from test import test_support +from base64 import b64encode + + def hexescape(char): """Escape char as RFC 2396 specifies""" hex_repr = hex(ord(char))[2:].upper() @@ -16,6 +20,43 @@ hex_repr = "0%s" % hex_repr return "%" + hex_repr + +class FakeHTTPMixin(object): + def fakehttp(self, fakedata): + class FakeSocket(StringIO.StringIO): + + def sendall(self, data): + FakeHTTPConnection.buf = data + + def makefile(self, *args, **kwds): + return self + + def read(self, amt=None): + if self.closed: + return "" + return StringIO.StringIO.read(self, amt) + + def readline(self, length=None): + if self.closed: + return "" + return StringIO.StringIO.readline(self, length) + + class FakeHTTPConnection(httplib.HTTPConnection): + + # buffer to store data for verification in urlopen tests. + buf = "" + + def connect(self): + self.sock = FakeSocket(fakedata) + + assert httplib.HTTP._connection_class == httplib.HTTPConnection + + httplib.HTTP._connection_class = FakeHTTPConnection + + def unfakehttp(self): + httplib.HTTP._connection_class = httplib.HTTPConnection + + class urlopen_FileTests(unittest.TestCase): """Test urlopen() opening a temporary file. @@ -44,7 +85,7 @@ # Make sure object returned by urlopen() has the specified methods for attr in ("read", "readline", "readlines", "fileno", "close", "info", "geturl", "getcode", "__iter__"): - self.assert_(hasattr(self.returned_obj, attr), + self.assertTrue(hasattr(self.returned_obj, attr), "object returned by urlopen() lacks %s attribute" % attr) @@ -66,9 +107,7 @@ def test_fileno(self): file_num = self.returned_obj.fileno() - if not test_support.is_jython: - self.assert_(isinstance(file_num, int), - "fileno() did not return an int") + self.assertIsInstance(file_num, int, "fileno() did not return an int") self.assertEqual(os.read(file_num, len(self.text)), self.text, "Reading on the file descriptor returned by fileno() " "did not return the expected text") @@ -79,7 +118,7 @@ self.returned_obj.close() def test_info(self): - self.assert_(isinstance(self.returned_obj.info(), mimetools.Message)) + self.assertIsInstance(self.returned_obj.info(), mimetools.Message) def test_geturl(self): self.assertEqual(self.returned_obj.geturl(), self.pathname) @@ -95,6 +134,9 @@ for line in self.returned_obj.__iter__(): self.assertEqual(line, self.text) + def test_relativelocalfile(self): + self.assertRaises(ValueError,urllib.urlopen,'./' + self.pathname) + class ProxyTests(unittest.TestCase): def setUp(self): @@ -114,31 +156,15 @@ self.env.set('NO_PROXY', 'localhost') proxies = urllib.getproxies_environment() # getproxies_environment use lowered case truncated (no '_proxy') keys - self.assertEquals('localhost', proxies['no']) + self.assertEqual('localhost', proxies['no']) + # List of no_proxies with space. + self.env.set('NO_PROXY', 'localhost, anotherdomain.com, newdomain.com') + self.assertTrue(urllib.proxy_bypass_environment('anotherdomain.com')) -class urlopen_HttpTests(unittest.TestCase): +class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin): """Test urlopen() opening a fake http connection.""" - def fakehttp(self, fakedata): - class FakeSocket(StringIO.StringIO): - def sendall(self, str): pass - def makefile(self, mode, name): return self - def read(self, amt=None): - if self.closed: return '' - return StringIO.StringIO.read(self, amt) - def readline(self, length=None): - if self.closed: return '' - return StringIO.StringIO.readline(self, length) - class FakeHTTPConnection(httplib.HTTPConnection): - def connect(self): - self.sock = FakeSocket(fakedata) - assert httplib.HTTP._connection_class == httplib.HTTPConnection - httplib.HTTP._connection_class = FakeHTTPConnection - - def unfakehttp(self): - httplib.HTTP._connection_class = httplib.HTTPConnection - def test_read(self): self.fakehttp('Hello!') try: @@ -150,6 +176,16 @@ finally: self.unfakehttp() + def test_url_fragment(self): + # Issue #11703: geturl() omits fragments in the original URL. + url = 'http://docs.python.org/library/urllib.html#OK' + self.fakehttp('Hello!') + try: + fp = urllib.urlopen(url) + self.assertEqual(fp.geturl(), url) + finally: + self.unfakehttp() + def test_read_bogus(self): # urlopen() should raise IOError for many error codes. self.fakehttp('''HTTP/1.1 401 Authentication Required @@ -186,6 +222,62 @@ finally: self.unfakehttp() + def test_missing_localfile(self): + self.assertRaises(IOError, urllib.urlopen, + 'file://localhost/a/missing/file.py') + fd, tmp_file = tempfile.mkstemp() + tmp_fileurl = 'file://localhost/' + tmp_file.replace(os.path.sep, '/') + try: + self.assertTrue(os.path.exists(tmp_file)) + fp = urllib.urlopen(tmp_fileurl) + finally: + os.close(fd) + fp.close() + os.unlink(tmp_file) + + self.assertFalse(os.path.exists(tmp_file)) + self.assertRaises(IOError, urllib.urlopen, tmp_fileurl) + + def test_ftp_nonexisting(self): + self.assertRaises(IOError, urllib.urlopen, + 'ftp://localhost/not/existing/file.py') + + + def test_userpass_inurl(self): + self.fakehttp('Hello!') + try: + fakehttp_wrapper = httplib.HTTP._connection_class + fp = urllib.urlopen("http://user:pass at python.org/") + authorization = ("Authorization: Basic %s\r\n" % + b64encode('user:pass')) + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf) + self.assertEqual(fp.readline(), "Hello!") + self.assertEqual(fp.readline(), "") + self.assertEqual(fp.geturl(), 'http://user:pass at python.org/') + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + def test_userpass_with_spaces_inurl(self): + self.fakehttp('Hello!') + try: + url = "http://a b:c d at python.org/" + fakehttp_wrapper = httplib.HTTP._connection_class + authorization = ("Authorization: Basic %s\r\n" % + b64encode('a b:c d')) + fp = urllib.urlopen(url) + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf) + self.assertEqual(fp.readline(), "Hello!") + self.assertEqual(fp.readline(), "") + # the spaces are quoted in URL so no match + self.assertNotEqual(fp.geturl(), url) + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + class urlretrieve_FileTests(unittest.TestCase): """Test urllib.urlretrieve() on local files""" @@ -243,9 +335,9 @@ # a headers value is returned. result = urllib.urlretrieve("file:%s" % test_support.TESTFN) self.assertEqual(result[0], test_support.TESTFN) - self.assert_(isinstance(result[1], mimetools.Message), - "did not get a mimetools.Message instance as second " - "returned value") + self.assertIsInstance(result[1], mimetools.Message, + "did not get a mimetools.Message instance as " + "second returned value") def test_copy(self): # Test that setting the filename argument works. @@ -254,7 +346,7 @@ result = urllib.urlretrieve(self.constructLocalFileUrl( test_support.TESTFN), second_temp) self.assertEqual(second_temp, result[0]) - self.assert_(os.path.exists(second_temp), "copy of the file was not " + self.assertTrue(os.path.exists(second_temp), "copy of the file was not " "made") FILE = file(second_temp, 'rb') try: @@ -268,9 +360,9 @@ def test_reporthook(self): # Make sure that the reporthook works. def hooktester(count, block_size, total_size, count_holder=[0]): - self.assert_(isinstance(count, int)) - self.assert_(isinstance(block_size, int)) - self.assert_(isinstance(total_size, int)) + self.assertIsInstance(count, int) + self.assertIsInstance(block_size, int) + self.assertIsInstance(total_size, int) self.assertEqual(count, count_holder[0]) count_holder[0] = count_holder[0] + 1 second_temp = "%s.2" % test_support.TESTFN @@ -318,6 +410,45 @@ self.assertEqual(report[0][1], 8192) self.assertEqual(report[0][2], 8193) + +class urlretrieve_HttpTests(unittest.TestCase, FakeHTTPMixin): + """Test urllib.urlretrieve() using fake http connections""" + + def test_short_content_raises_ContentTooShortError(self): + self.fakehttp('''HTTP/1.1 200 OK +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Connection: close +Content-Length: 100 +Content-Type: text/html; charset=iso-8859-1 + +FF +''') + + def _reporthook(par1, par2, par3): + pass + + try: + self.assertRaises(urllib.ContentTooShortError, urllib.urlretrieve, + 'http://example.com', reporthook=_reporthook) + finally: + self.unfakehttp() + + def test_short_content_raises_ContentTooShortError_without_reporthook(self): + self.fakehttp('''HTTP/1.1 200 OK +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Connection: close +Content-Length: 100 +Content-Type: text/html; charset=iso-8859-1 + +FF +''') + try: + self.assertRaises(urllib.ContentTooShortError, urllib.urlretrieve, 'http://example.com/') + finally: + self.unfakehttp() + class QuotingTests(unittest.TestCase): """Tests for urllib.quote() and urllib.quote_plus() @@ -395,8 +526,10 @@ result = urllib.quote(partial_quote) self.assertEqual(expected, result, "using quote(): %s != %s" % (expected, result)) + result = urllib.quote_plus(partial_quote) self.assertEqual(expected, result, "using quote_plus(): %s != %s" % (expected, result)) + self.assertRaises(TypeError, urllib.quote, None) def test_quoting_space(self): # Make sure quote() and quote_plus() handle spaces as specified in @@ -527,7 +660,7 @@ expect_somewhere = ["1st=1", "2nd=2", "3rd=3"] result = urllib.urlencode(given) for expected in expect_somewhere: - self.assert_(expected in result, + self.assertIn(expected, result, "testing %s: %s not found in %s" % (test_type, expected, result)) self.assertEqual(result.count('&'), 2, @@ -536,7 +669,7 @@ amp_location = result.index('&') on_amp_left = result[amp_location - 1] on_amp_right = result[amp_location + 1] - self.assert_(on_amp_left.isdigit() and on_amp_right.isdigit(), + self.assertTrue(on_amp_left.isdigit() and on_amp_right.isdigit(), "testing %s: '&' not located in proper place in %s" % (test_type, result)) self.assertEqual(len(result), (5 * 3) + 2, #5 chars per thing and amps @@ -574,8 +707,7 @@ result = urllib.urlencode(given, True) for value in given["sequence"]: expect = "sequence=%s" % value - self.assert_(expect in result, - "%s not found in %s" % (expect, result)) + self.assertIn(expect, result) self.assertEqual(result.count('&'), 2, "Expected 2 '&'s, got %s" % result.count('&')) @@ -622,8 +754,45 @@ "url2pathname() failed; %s != %s" % (expect, result)) + @unittest.skipUnless(sys.platform == 'win32', + 'test specific to the nturl2path library') + def test_ntpath(self): + given = ('/C:/', '///C:/', '/C|//') + expect = 'C:\\' + for url in given: + result = urllib.url2pathname(url) + self.assertEqual(expect, result, + 'nturl2path.url2pathname() failed; %s != %s' % + (expect, result)) + given = '///C|/path' + expect = 'C:\\path' + result = urllib.url2pathname(given) + self.assertEqual(expect, result, + 'nturl2path.url2pathname() failed; %s != %s' % + (expect, result)) + +class Utility_Tests(unittest.TestCase): + """Testcase to test the various utility functions in the urllib.""" + + def test_splitpasswd(self): + """Some of the password examples are not sensible, but it is added to + confirming to RFC2617 and addressing issue4675. + """ + self.assertEqual(('user', 'ab'),urllib.splitpasswd('user:ab')) + self.assertEqual(('user', 'a\nb'),urllib.splitpasswd('user:a\nb')) + self.assertEqual(('user', 'a\tb'),urllib.splitpasswd('user:a\tb')) + self.assertEqual(('user', 'a\rb'),urllib.splitpasswd('user:a\rb')) + self.assertEqual(('user', 'a\fb'),urllib.splitpasswd('user:a\fb')) + self.assertEqual(('user', 'a\vb'),urllib.splitpasswd('user:a\vb')) + self.assertEqual(('user', 'a:b'),urllib.splitpasswd('user:a:b')) + self.assertEqual(('user', 'a b'),urllib.splitpasswd('user:a b')) + self.assertEqual(('user 2', 'ab'),urllib.splitpasswd('user 2:ab')) + self.assertEqual(('user+1', 'a+b'),urllib.splitpasswd('user+1:a+b')) + + class URLopener_Tests(unittest.TestCase): """Testcase to test the open method of URLopener class.""" + def test_quoted_open(self): class DummyURLopener(urllib.URLopener): def open_spam(self, url): @@ -640,7 +809,7 @@ # Just commented them out. # Can't really tell why keep failing in windows and sparc. -# Everywhere else they work ok, but on those machines, someteimes +# Everywhere else they work ok, but on those machines, sometimes # fail in one of the tests, sometimes in other. I have a linux, and # the tests go ok. # If anybody has one of the problematic enviroments, please help! @@ -689,7 +858,7 @@ # def testTimeoutNone(self): # # global default timeout is ignored # import socket -# self.assert_(socket.getdefaulttimeout() is None) +# self.assertTrue(socket.getdefaulttimeout() is None) # socket.setdefaulttimeout(30) # try: # ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) @@ -701,7 +870,7 @@ # def testTimeoutDefault(self): # # global default timeout is used # import socket -# self.assert_(socket.getdefaulttimeout() is None) +# self.assertTrue(socket.getdefaulttimeout() is None) # socket.setdefaulttimeout(30) # try: # ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) @@ -727,11 +896,13 @@ urlopen_FileTests, urlopen_HttpTests, urlretrieve_FileTests, + urlretrieve_HttpTests, ProxyTests, QuotingTests, UnquotingTests, urlencode_Tests, Pathname_Tests, + Utility_Tests, URLopener_Tests, #FTPWrapperTests, ) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -293,6 +293,7 @@ self._tunnel_headers = headers else: self._tunnel_headers.clear() + def request(self, method, url, body=None, headers=None): self.method = method self.selector = url @@ -304,10 +305,12 @@ if self.raise_on_endheaders: import socket raise socket.error() + def getresponse(self): return MockHTTPResponse(MockFile(), {}, 200, "OK") - def close(self): pass + def close(self): + pass class MockHandler: # useful for testing handler machinery @@ -595,21 +598,20 @@ def sanepathname2url(path): import urllib urlpath = urllib.pathname2url(path) - if ((os._name if test_support.is_jython else os.name) == 'nt' - and urlpath.startswith("///")): + if os.name == "nt" and urlpath.startswith("///"): urlpath = urlpath[2:] # XXX don't ask me about the mac... return urlpath class HandlerTests(unittest.TestCase): - @unittest.skipIf(test_support.is_jython, "Required SSL support not yet available on jython") def test_ftp(self): class MockFTPWrapper: def __init__(self, data): self.data = data def retrfile(self, filename, filetype): self.filename, self.filetype = filename, filetype return StringIO.StringIO(self.data), len(self.data) + def close(self): pass class NullFTPHandler(urllib2.FTPHandler): def __init__(self, data): self.data = data @@ -661,7 +663,6 @@ self.assertEqual(headers.get("Content-type"), mimetype) self.assertEqual(int(headers["Content-length"]), len(data)) - @unittest.skip("FIXME: not working") def test_file(self): import rfc822, socket h = urllib2.FileHandler() @@ -974,6 +975,28 @@ self.assertEqual(count, urllib2.HTTPRedirectHandler.max_redirections) + def test_invalid_redirect(self): + from_url = "http://example.com/a.html" + valid_schemes = ['http', 'https', 'ftp'] + invalid_schemes = ['file', 'imap', 'ldap'] + schemeless_url = "example.com/b.html" + h = urllib2.HTTPRedirectHandler() + o = h.parent = MockOpener() + req = Request(from_url) + req.timeout = socket._GLOBAL_DEFAULT_TIMEOUT + + for scheme in invalid_schemes: + invalid_url = scheme + '://' + schemeless_url + self.assertRaises(urllib2.HTTPError, h.http_error_302, + req, MockFile(), 302, "Security Loophole", + MockHeaders({"location": invalid_url})) + + for scheme in valid_schemes: + valid_url = scheme + '://' + schemeless_url + h.http_error_302(req, MockFile(), 302, "That's fine", + MockHeaders({"location": valid_url})) + self.assertEqual(o.req.get_full_url(), valid_url) + def test_cookie_redirect(self): # cookies shouldn't leak into redirected requests from cookielib import CookieJar @@ -990,6 +1013,15 @@ o.open("http://www.example.com/") self.assertTrue(not hh.req.has_header("Cookie")) + def test_redirect_fragment(self): + redirected_url = 'http://www.example.com/index.html#OK\r\n\r\n' + hh = MockHTTPHandler(302, 'Location: ' + redirected_url) + hdeh = urllib2.HTTPDefaultErrorHandler() + hrh = urllib2.HTTPRedirectHandler() + o = build_test_opener(hh, hdeh, hrh) + fp = o.open('http://www.example.com') + self.assertEqual(fp.geturl(), redirected_url.strip()) + def test_proxy(self): o = OpenerDirector() ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128")) @@ -1074,12 +1106,30 @@ self._test_basic_auth(opener, auth_handler, "Authorization", realm, http_handler, password_manager, "http://acme.example.com/protected", - "http://acme.example.com/protected", - ) + "http://acme.example.com/protected" + ) def test_basic_auth_with_single_quoted_realm(self): self.test_basic_auth(quote_char="'") + def test_basic_auth_with_unquoted_realm(self): + opener = OpenerDirector() + password_manager = MockPasswordManager() + auth_handler = urllib2.HTTPBasicAuthHandler(password_manager) + realm = "ACME Widget Store" + http_handler = MockHTTPHandler( + 401, 'WWW-Authenticate: Basic realm=%s\r\n\r\n' % realm) + opener.add_handler(auth_handler) + opener.add_handler(http_handler) + msg = "Basic Auth Realm was unquoted" + with test_support.check_warnings((msg, UserWarning)): + self._test_basic_auth(opener, auth_handler, "Authorization", + realm, http_handler, password_manager, + "http://acme.example.com/protected", + "http://acme.example.com/protected" + ) + + def test_proxy_basic_auth(self): opener = OpenerDirector() ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128")) @@ -1098,7 +1148,7 @@ ) def test_basic_and_digest_auth_handlers(self): - # HTTPDigestAuthHandler threw an exception if it couldn't handle a 40* + # HTTPDigestAuthHandler raised an exception if it couldn't handle a 40* # response (http://python.org/sf/1479302), where it should instead # return None to allow another handler (especially # HTTPBasicAuthHandler) to handle the response. @@ -1275,12 +1325,43 @@ req = Request("") self.assertEqual("www.python.org", req.get_host()) - def test_urlwith_fragment(self): + def test_url_fragment(self): req = Request("http://www.python.org/?qs=query#fragment=true") self.assertEqual("/?qs=query", req.get_selector()) req = Request("http://www.python.org/#fun=true") self.assertEqual("/", req.get_selector()) + # Issue 11703: geturl() omits fragment in the original URL. + url = 'http://docs.python.org/library/urllib2.html#OK' + req = Request(url) + self.assertEqual(req.get_full_url(), url) + + def test_HTTPError_interface(self): + """ + Issue 13211 reveals that HTTPError didn't implement the URLError + interface even though HTTPError is a subclass of URLError. + + >>> err = urllib2.HTTPError(msg='something bad happened', url=None, code=None, hdrs=None, fp=None) + >>> assert hasattr(err, 'reason') + >>> err.reason + 'something bad happened' + """ + + def test_HTTPError_interface_call(self): + """ + Issue 15701= - HTTPError interface has info method available from URLError. + """ + err = urllib2.HTTPError(msg='something bad happened', url=None, + code=None, hdrs='Content-Length:42', fp=None) + self.assertTrue(hasattr(err, 'reason')) + assert hasattr(err, 'reason') + assert hasattr(err, 'info') + assert callable(err.info) + try: + err.info() + except AttributeError: + self.fail("err.info() failed") + self.assertEqual(err.info(), "Content-Length:42") def test_main(verbose=None): from test import test_urllib2 diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -1,14 +1,16 @@ #!/usr/bin/env python -import sys -import threading import urlparse import urllib2 import BaseHTTPServer import unittest import hashlib + from test import test_support +mimetools = test_support.import_module('mimetools', deprecated=True) +threading = test_support.import_module('threading') + # Loopback http server infrastructure class LoopbackHttpServer(BaseHTTPServer.HTTPServer): @@ -40,13 +42,16 @@ class LoopbackHttpServerThread(threading.Thread): """Stoppable thread that runs a loopback http server.""" - def __init__(self, port, RequestHandlerClass): + def __init__(self, request_handler): threading.Thread.__init__(self) - self._RequestHandlerClass = RequestHandlerClass self._stop = False - self._port = port - self._server_address = ('127.0.0.1', self._port) self.ready = threading.Event() + request_handler.protocol_version = "HTTP/1.0" + self.httpd = LoopbackHttpServer(('127.0.0.1', 0), + request_handler) + #print "Serving HTTP on %s port %s" % (self.httpd.server_name, + # self.httpd.server_port) + self.port = self.httpd.server_port def stop(self): """Stops the webserver if it's currently running.""" @@ -57,19 +62,9 @@ self.join() def run(self): - protocol = "HTTP/1.0" - - self._RequestHandlerClass.protocol_version = protocol - httpd = LoopbackHttpServer(self._server_address, - self._RequestHandlerClass) - - sa = httpd.socket.getsockname() - #print "Serving HTTP on", sa[0], "port", sa[1], "..." - self.ready.set() while not self._stop: - httpd.handle_request() - httpd.server_close() + self.httpd.handle_request() # Authentication infrastructure @@ -161,13 +156,13 @@ if len(self._users) == 0: return True - if not request_handler.headers.has_key('Proxy-Authorization'): + if 'Proxy-Authorization' not in request_handler.headers: return self._return_auth_challenge(request_handler) else: auth_dict = self._create_auth_dict( request_handler.headers['Proxy-Authorization'] ) - if self._users.has_key(auth_dict["username"]): + if auth_dict["username"] in self._users: password = self._users[ auth_dict["username"] ] else: return self._return_auth_challenge(request_handler) @@ -202,7 +197,11 @@ testing. """ - digest_auth_handler = DigestAuthHandler() + def __init__(self, digest_auth_handler, *args, **kwargs): + # This has to be set before calling our parent's __init__(), which will + # try to call do_GET(). + self.digest_auth_handler = digest_auth_handler + BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs) def log_message(self, format, *args): # Uncomment the next line for debugging. @@ -223,60 +222,68 @@ # Test cases -class ProxyAuthTests(unittest.TestCase): - URL = "http://www.foo.com" +class BaseTestCase(unittest.TestCase): + def setUp(self): + self._threads = test_support.threading_setup() - PORT = 58080 + def tearDown(self): + test_support.threading_cleanup(*self._threads) + + +class ProxyAuthTests(BaseTestCase): + URL = "http://localhost" + USER = "tester" PASSWD = "test123" REALM = "TestRealm" - PROXY_URL = "http://127.0.0.1:%d" % PORT + def setUp(self): + super(ProxyAuthTests, self).setUp() + self.digest_auth_handler = DigestAuthHandler() + self.digest_auth_handler.set_users({self.USER: self.PASSWD}) + self.digest_auth_handler.set_realm(self.REALM) + def create_fake_proxy_handler(*args, **kwargs): + return FakeProxyHandler(self.digest_auth_handler, *args, **kwargs) - def setUp(self): - FakeProxyHandler.digest_auth_handler.set_users({ - self.USER : self.PASSWD - }) - FakeProxyHandler.digest_auth_handler.set_realm(self.REALM) - - self.server = LoopbackHttpServerThread(self.PORT, FakeProxyHandler) + self.server = LoopbackHttpServerThread(create_fake_proxy_handler) self.server.start() self.server.ready.wait() - - handler = urllib2.ProxyHandler({"http" : self.PROXY_URL}) - self._digest_auth_handler = urllib2.ProxyDigestAuthHandler() - self.opener = urllib2.build_opener(handler, self._digest_auth_handler) + proxy_url = "http://127.0.0.1:%d" % self.server.port + handler = urllib2.ProxyHandler({"http" : proxy_url}) + self.proxy_digest_handler = urllib2.ProxyDigestAuthHandler() + self.opener = urllib2.build_opener(handler, self.proxy_digest_handler) def tearDown(self): self.server.stop() + super(ProxyAuthTests, self).tearDown() def test_proxy_with_bad_password_raises_httperror(self): - self._digest_auth_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD+"bad") - FakeProxyHandler.digest_auth_handler.set_qop("auth") + self.digest_auth_handler.set_qop("auth") self.assertRaises(urllib2.HTTPError, self.opener.open, self.URL) def test_proxy_with_no_password_raises_httperror(self): - FakeProxyHandler.digest_auth_handler.set_qop("auth") + self.digest_auth_handler.set_qop("auth") self.assertRaises(urllib2.HTTPError, self.opener.open, self.URL) def test_proxy_qop_auth_works(self): - self._digest_auth_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD) - FakeProxyHandler.digest_auth_handler.set_qop("auth") + self.digest_auth_handler.set_qop("auth") result = self.opener.open(self.URL) while result.read(): pass result.close() def test_proxy_qop_auth_int_works_or_throws_urlerror(self): - self._digest_auth_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD) - FakeProxyHandler.digest_auth_handler.set_qop("auth-int") + self.digest_auth_handler.set_qop("auth-int") try: result = self.opener.open(self.URL) except urllib2.URLError: @@ -289,6 +296,244 @@ pass result.close() + +def GetRequestHandler(responses): + + class FakeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + + server_version = "TestHTTP/" + requests = [] + headers_received = [] + port = 80 + + def do_GET(self): + body = self.send_head() + if body: + self.wfile.write(body) + + def do_POST(self): + content_length = self.headers['Content-Length'] + post_data = self.rfile.read(int(content_length)) + self.do_GET() + self.requests.append(post_data) + + def send_head(self): + FakeHTTPRequestHandler.headers_received = self.headers + self.requests.append(self.path) + response_code, headers, body = responses.pop(0) + + self.send_response(response_code) + + for (header, value) in headers: + self.send_header(header, value % self.port) + if body: + self.send_header('Content-type', 'text/plain') + self.end_headers() + return body + self.end_headers() + + def log_message(self, *args): + pass + + + return FakeHTTPRequestHandler + + +class TestUrlopen(BaseTestCase): + """Tests urllib2.urlopen using the network. + + These tests are not exhaustive. Assuming that testing using files does a + good job overall of some of the basic interface features. There are no + tests exercising the optional 'data' and 'proxies' arguments. No tests + for transparent redirection have been written. + """ + + def setUp(self): + proxy_handler = urllib2.ProxyHandler({}) + opener = urllib2.build_opener(proxy_handler) + urllib2.install_opener(opener) + super(TestUrlopen, self).setUp() + + def start_server(self, responses): + handler = GetRequestHandler(responses) + + self.server = LoopbackHttpServerThread(handler) + self.server.start() + self.server.ready.wait() + port = self.server.port + handler.port = port + return handler + + + def test_redirection(self): + expected_response = 'We got here...' + responses = [ + (302, [('Location', 'http://localhost:%s/somewhere_else')], ''), + (200, [], expected_response) + ] + + handler = self.start_server(responses) + + try: + f = urllib2.urlopen('http://localhost:%s/' % handler.port) + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/', '/somewhere_else']) + finally: + self.server.stop() + + + def test_404(self): + expected_response = 'Bad bad bad...' + handler = self.start_server([(404, [], expected_response)]) + + try: + try: + urllib2.urlopen('http://localhost:%s/weeble' % handler.port) + except urllib2.URLError, f: + pass + else: + self.fail('404 should raise URLError') + + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/weeble']) + finally: + self.server.stop() + + + def test_200(self): + expected_response = 'pycon 2008...' + handler = self.start_server([(200, [], expected_response)]) + + try: + f = urllib2.urlopen('http://localhost:%s/bizarre' % handler.port) + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/bizarre']) + finally: + self.server.stop() + + def test_200_with_parameters(self): + expected_response = 'pycon 2008...' + handler = self.start_server([(200, [], expected_response)]) + + try: + f = urllib2.urlopen('http://localhost:%s/bizarre' % handler.port, 'get=with_feeling') + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/bizarre', 'get=with_feeling']) + finally: + self.server.stop() + + + def test_sending_headers(self): + handler = self.start_server([(200, [], "we don't care")]) + + try: + req = urllib2.Request("http://localhost:%s/" % handler.port, + headers={'Range': 'bytes=20-39'}) + urllib2.urlopen(req) + self.assertEqual(handler.headers_received['Range'], 'bytes=20-39') + finally: + self.server.stop() + + def test_basic(self): + handler = self.start_server([(200, [], "we don't care")]) + + try: + open_url = urllib2.urlopen("http://localhost:%s" % handler.port) + for attr in ("read", "close", "info", "geturl"): + self.assertTrue(hasattr(open_url, attr), "object returned from " + "urlopen lacks the %s attribute" % attr) + try: + self.assertTrue(open_url.read(), "calling 'read' failed") + finally: + open_url.close() + finally: + self.server.stop() + + def test_info(self): + handler = self.start_server([(200, [], "we don't care")]) + + try: + open_url = urllib2.urlopen("http://localhost:%s" % handler.port) + info_obj = open_url.info() + self.assertIsInstance(info_obj, mimetools.Message, + "object returned by 'info' is not an " + "instance of mimetools.Message") + self.assertEqual(info_obj.getsubtype(), "plain") + finally: + self.server.stop() + + def test_geturl(self): + # Make sure same URL as opened is returned by geturl. + handler = self.start_server([(200, [], "we don't care")]) + + try: + open_url = urllib2.urlopen("http://localhost:%s" % handler.port) + url = open_url.geturl() + self.assertEqual(url, "http://localhost:%s" % handler.port) + finally: + self.server.stop() + + + def test_bad_address(self): + # Make sure proper exception is raised when connecting to a bogus + # address. + self.assertRaises(IOError, + # Given that both VeriSign and various ISPs have in + # the past or are presently hijacking various invalid + # domain name requests in an attempt to boost traffic + # to their own sites, finding a domain name to use + # for this test is difficult. RFC2606 leads one to + # believe that '.invalid' should work, but experience + # seemed to indicate otherwise. Single character + # TLDs are likely to remain invalid, so this seems to + # be the best choice. The trailing '.' prevents a + # related problem: The normal DNS resolver appends + # the domain names from the search path if there is + # no '.' the end and, and if one of those domains + # implements a '*' rule a result is returned. + # However, none of this will prevent the test from + # failing if the ISP hijacks all invalid domain + # requests. The real solution would be to be able to + # parameterize the framework with a mock resolver. + urllib2.urlopen, "http://sadflkjsasf.i.nvali.d./") + + def test_iteration(self): + expected_response = "pycon 2008..." + handler = self.start_server([(200, [], expected_response)]) + try: + data = urllib2.urlopen("http://localhost:%s" % handler.port) + for line in data: + self.assertEqual(line, expected_response) + finally: + self.server.stop() + + def ztest_line_iteration(self): + lines = ["We\n", "got\n", "here\n", "verylong " * 8192 + "\n"] + expected_response = "".join(lines) + handler = self.start_server([(200, [], expected_response)]) + try: + data = urllib2.urlopen("http://localhost:%s" % handler.port) + for index, line in enumerate(data): + self.assertEqual(line, lines[index], + "Fetched line number %s doesn't match expected:\n" + " Expected length was %s, got %s" % + (index, len(lines[index]), len(line))) + finally: + self.server.stop() + self.assertEqual(index + 1, len(lines)) + def test_main(): # We will NOT depend on the network resource flag # (Lib/test/regrtest.py -u network) since all tests here are only @@ -296,7 +541,7 @@ # the next line. #test_support.requires("network") - test_support.run_unittest(ProxyAuthTests) + test_support.run_unittest(ProxyAuthTests, TestUrlopen) if __name__ == "__main__": test_main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 2 16:20:11 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 2 Feb 2013 16:20:11 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Apply_jython_specfic_change?= =?utf-8?q?s?= Message-ID: <3YyzQ35pJ2zSLf@mail.python.org> http://hg.python.org/jython/rev/01a1add2ad8e changeset: 6980:01a1add2ad8e user: Alan Kennedy date: Sat Feb 02 15:18:49 2013 +0000 summary: Apply jython specfic changes files: Lib/test/test_urllib.py | 5 ++++- Lib/test/test_urllib2.py | 3 +++ Lib/test/test_urllib2_localnet.py | 12 +++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -107,7 +107,9 @@ def test_fileno(self): file_num = self.returned_obj.fileno() - self.assertIsInstance(file_num, int, "fileno() did not return an int") + if not test_support.is_jython: + self.assert_(isinstance(file_num, int), + "fileno() did not return an int") self.assertEqual(os.read(file_num, len(self.text)), self.text, "Reading on the file descriptor returned by fileno() " "did not return the expected text") @@ -238,6 +240,7 @@ self.assertFalse(os.path.exists(tmp_file)) self.assertRaises(IOError, urllib.urlopen, tmp_fileurl) + @unittest.skipIf(test_support.is_jython, "Required SSL support not yet available on jython") def test_ftp_nonexisting(self): self.assertRaises(IOError, urllib.urlopen, 'ftp://localhost/not/existing/file.py') diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -605,6 +605,7 @@ class HandlerTests(unittest.TestCase): + @unittest.skipIf(test_support.is_jython, "Required SSL support not yet available on jython") def test_ftp(self): class MockFTPWrapper: def __init__(self, data): self.data = data @@ -1112,6 +1113,7 @@ def test_basic_auth_with_single_quoted_realm(self): self.test_basic_auth(quote_char="'") + @unittest.skipIf(test_support.is_jython, "Currently not working on jython") def test_basic_auth_with_unquoted_realm(self): opener = OpenerDirector() password_manager = MockPasswordManager() @@ -1347,6 +1349,7 @@ 'something bad happened' """ + @unittest.skip("Test is broken because of fp=None, which causes failure to call addinfourl superclass __init__") def test_HTTPError_interface_call(self): """ Issue 15701= - HTTPError interface has info method available from URLError. diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -8,6 +8,11 @@ from test import test_support +if test_support.is_jython: + import socket + # Working around an IPV6 problem on Windows + socket._use_ipv4_addresses_only(True) + mimetools = test_support.import_module('mimetools', deprecated=True) threading = test_support.import_module('threading') @@ -21,7 +26,12 @@ def __init__(self, server_address, RequestHandlerClass): BaseHTTPServer.HTTPServer.__init__(self, server_address, - RequestHandlerClass) + RequestHandlerClass, + True) + + host, port = self.socket.getsockname()[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port # Set the timeout of our listening socket really low so # that we can stop the server easily. -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 3 15:01:46 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 3 Feb 2013 15:01:46 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_adding_latest_ftplib=2Epy_f?= =?utf-8?q?rom_cpython_2=2E7=3A?= Message-ID: <3YzYd63NzHzPx0@mail.python.org> http://hg.python.org/jython/rev/e22f0f0821a1 changeset: 6981:e22f0f0821a1 user: Alan Kennedy date: Sun Feb 03 13:50:35 2013 +0000 summary: adding latest ftplib.py from cpython 2.7: http://hg.python.org/cpython/file/b6b707063991/Lib/ftplib.py files: Lib/ftplib.py | 1046 +++++++++++++++++++++++++++++++++++++ 1 files changed, 1046 insertions(+), 0 deletions(-) diff --git a/Lib/ftplib.py b/Lib/ftplib.py new file mode 100644 --- /dev/null +++ b/Lib/ftplib.py @@ -0,0 +1,1046 @@ +"""An FTP client class and some helper functions. + +Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds + +Example: + +>>> from ftplib import FTP +>>> ftp = FTP('ftp.python.org') # connect to host, default port +>>> ftp.login() # default, i.e.: user anonymous, passwd anonymous@ +'230 Guest login ok, access restrictions apply.' +>>> ftp.retrlines('LIST') # list directory contents +total 9 +drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . +drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. +drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin +drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc +d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming +drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib +drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub +drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr +-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg +'226 Transfer complete.' +>>> ftp.quit() +'221 Goodbye.' +>>> + +A nice test that reveals some of the network dialogue would be: +python ftplib.py -d localhost -l -p -l +""" + +# +# Changes and improvements suggested by Steve Majewski. +# Modified by Jack to work on the mac. +# Modified by Siebren to support docstrings and PASV. +# Modified by Phil Schwartz to add storbinary and storlines callbacks. +# Modified by Giampaolo Rodola' to add TLS support. +# + +import os +import sys + +# Import SOCKS module if it exists, else standard socket module socket +try: + import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket + from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn +except ImportError: + import socket +from socket import _GLOBAL_DEFAULT_TIMEOUT + +__all__ = ["FTP","Netrc"] + +# Magic number from +MSG_OOB = 0x1 # Process data out of band + + +# The standard FTP server control port +FTP_PORT = 21 + + +# Exception raised when an error or invalid response is received +class Error(Exception): pass +class error_reply(Error): pass # unexpected [123]xx reply +class error_temp(Error): pass # 4xx errors +class error_perm(Error): pass # 5xx errors +class error_proto(Error): pass # response does not begin with [1-5] + + +# All exceptions (hopefully) that may be raised here and that aren't +# (always) programming errors on our side +all_errors = (Error, IOError, EOFError) + + +# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) +CRLF = '\r\n' + +# The class itself +class FTP: + + '''An FTP client class. + + To create a connection, call the class using these arguments: + host, user, passwd, acct, timeout + + The first four arguments are all strings, and have default value ''. + timeout must be numeric and defaults to None if not passed, + meaning that no timeout will be set on any ftp socket(s) + If a timeout is passed, then this is now the default timeout for all ftp + socket operations for this instance. + + Then use self.connect() with optional host and port argument. + + To download a file, use ftp.retrlines('RETR ' + filename), + or ftp.retrbinary() with slightly different arguments. + To upload a file, use ftp.storlines() or ftp.storbinary(), + which have an open file as argument (see their definitions + below for details). + The download/upload functions first issue appropriate TYPE + and PORT or PASV commands. +''' + + debugging = 0 + host = '' + port = FTP_PORT + sock = None + file = None + welcome = None + passiveserver = 1 + + # Initialization method (called by class instantiation). + # Initialize host to localhost, port to standard ftp port + # Optional arguments are host (for connect()), + # and user, passwd, acct (for login()) + def __init__(self, host='', user='', passwd='', acct='', + timeout=_GLOBAL_DEFAULT_TIMEOUT): + self.timeout = timeout + if host: + self.connect(host) + if user: + self.login(user, passwd, acct) + + def connect(self, host='', port=0, timeout=-999): + '''Connect to host. Arguments are: + - host: hostname to connect to (string, default previous host) + - port: port to connect to (integer, default previous port) + ''' + if host != '': + self.host = host + if port > 0: + self.port = port + if timeout != -999: + self.timeout = timeout + self.sock = socket.create_connection((self.host, self.port), self.timeout) + self.af = self.sock.family + self.file = self.sock.makefile('rb') + self.welcome = self.getresp() + return self.welcome + + def getwelcome(self): + '''Get the welcome message from the server. + (this is read and squirreled away by connect())''' + if self.debugging: + print '*welcome*', self.sanitize(self.welcome) + return self.welcome + + def set_debuglevel(self, level): + '''Set the debugging level. + The required argument level means: + 0: no debugging output (default) + 1: print commands and responses but not body text etc. + 2: also print raw lines read and sent before stripping CR/LF''' + self.debugging = level + debug = set_debuglevel + + def set_pasv(self, val): + '''Use passive or active mode for data transfers. + With a false argument, use the normal PORT mode, + With a true argument, use the PASV command.''' + self.passiveserver = val + + # Internal: "sanitize" a string for printing + def sanitize(self, s): + if s[:5] == 'pass ' or s[:5] == 'PASS ': + i = len(s) + while i > 5 and s[i-1] in '\r\n': + i = i-1 + s = s[:5] + '*'*(i-5) + s[i:] + return repr(s) + + # Internal: send one line to the server, appending CRLF + def putline(self, line): + line = line + CRLF + if self.debugging > 1: print '*put*', self.sanitize(line) + self.sock.sendall(line) + + # Internal: send one command to the server (through putline()) + def putcmd(self, line): + if self.debugging: print '*cmd*', self.sanitize(line) + self.putline(line) + + # Internal: return one line from the server, stripping CRLF. + # Raise EOFError if the connection is closed + def getline(self): + line = self.file.readline() + if self.debugging > 1: + print '*get*', self.sanitize(line) + if not line: raise EOFError + if line[-2:] == CRLF: line = line[:-2] + elif line[-1:] in CRLF: line = line[:-1] + return line + + # Internal: get a response from the server, which may possibly + # consist of multiple lines. Return a single string with no + # trailing CRLF. If the response consists of multiple lines, + # these are separated by '\n' characters in the string + def getmultiline(self): + line = self.getline() + if line[3:4] == '-': + code = line[:3] + while 1: + nextline = self.getline() + line = line + ('\n' + nextline) + if nextline[:3] == code and \ + nextline[3:4] != '-': + break + return line + + # Internal: get a response from the server. + # Raise various errors if the response indicates an error + def getresp(self): + resp = self.getmultiline() + if self.debugging: print '*resp*', self.sanitize(resp) + self.lastresp = resp[:3] + c = resp[:1] + if c in ('1', '2', '3'): + return resp + if c == '4': + raise error_temp, resp + if c == '5': + raise error_perm, resp + raise error_proto, resp + + def voidresp(self): + """Expect a response beginning with '2'.""" + resp = self.getresp() + if resp[:1] != '2': + raise error_reply, resp + return resp + + def abort(self): + '''Abort a file transfer. Uses out-of-band data. + This does not follow the procedure from the RFC to send Telnet + IP and Synch; that doesn't seem to work with the servers I've + tried. Instead, just send the ABOR command as OOB data.''' + line = 'ABOR' + CRLF + if self.debugging > 1: print '*put urgent*', self.sanitize(line) + self.sock.sendall(line, MSG_OOB) + resp = self.getmultiline() + if resp[:3] not in ('426', '225', '226'): + raise error_proto, resp + + def sendcmd(self, cmd): + '''Send a command and return the response.''' + self.putcmd(cmd) + return self.getresp() + + def voidcmd(self, cmd): + """Send a command and expect a response beginning with '2'.""" + self.putcmd(cmd) + return self.voidresp() + + def sendport(self, host, port): + '''Send a PORT command with the current host and the given + port number. + ''' + hbytes = host.split('.') + pbytes = [repr(port//256), repr(port%256)] + bytes = hbytes + pbytes + cmd = 'PORT ' + ','.join(bytes) + return self.voidcmd(cmd) + + def sendeprt(self, host, port): + '''Send a EPRT command with the current host and the given port number.''' + af = 0 + if self.af == socket.AF_INET: + af = 1 + if self.af == socket.AF_INET6: + af = 2 + if af == 0: + raise error_proto, 'unsupported address family' + fields = ['', repr(af), host, repr(port), ''] + cmd = 'EPRT ' + '|'.join(fields) + return self.voidcmd(cmd) + + def makeport(self): + '''Create a new socket and send a PORT command for it.''' + err = None + sock = None + for res in socket.getaddrinfo(None, 0, self.af, socket.SOCK_STREAM, 0, socket.AI_PASSIVE): + af, socktype, proto, canonname, sa = res + try: + sock = socket.socket(af, socktype, proto) + sock.bind(sa) + except socket.error, err: + if sock: + sock.close() + sock = None + continue + break + if sock is None: + if err is not None: + raise err + else: + raise socket.error("getaddrinfo returns an empty list") + sock.listen(1) + port = sock.getsockname()[1] # Get proper port + host = self.sock.getsockname()[0] # Get proper host + if self.af == socket.AF_INET: + resp = self.sendport(host, port) + else: + resp = self.sendeprt(host, port) + if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(self.timeout) + return sock + + def makepasv(self): + if self.af == socket.AF_INET: + host, port = parse227(self.sendcmd('PASV')) + else: + host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername()) + return host, port + + def ntransfercmd(self, cmd, rest=None): + """Initiate a transfer over the data connection. + + If the transfer is active, send a port command and the + transfer command, and accept the connection. If the server is + passive, send a pasv command, connect to it, and start the + transfer command. Either way, return the socket for the + connection and the expected size of the transfer. The + expected size may be None if it could not be determined. + + Optional `rest' argument can be a string that is sent as the + argument to a REST command. This is essentially a server + marker used to tell the server to skip over any data up to the + given marker. + """ + size = None + if self.passiveserver: + host, port = self.makepasv() + conn = socket.create_connection((host, port), self.timeout) + try: + if rest is not None: + self.sendcmd("REST %s" % rest) + resp = self.sendcmd(cmd) + # Some servers apparently send a 200 reply to + # a LIST or STOR command, before the 150 reply + # (and way before the 226 reply). This seems to + # be in violation of the protocol (which only allows + # 1xx or error messages for LIST), so we just discard + # this response. + if resp[0] == '2': + resp = self.getresp() + if resp[0] != '1': + raise error_reply, resp + except: + conn.close() + raise + else: + sock = self.makeport() + try: + if rest is not None: + self.sendcmd("REST %s" % rest) + resp = self.sendcmd(cmd) + # See above. + if resp[0] == '2': + resp = self.getresp() + if resp[0] != '1': + raise error_reply, resp + conn, sockaddr = sock.accept() + if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT: + conn.settimeout(self.timeout) + finally: + sock.close() + if resp[:3] == '150': + # this is conditional in case we received a 125 + size = parse150(resp) + return conn, size + + def transfercmd(self, cmd, rest=None): + """Like ntransfercmd() but returns only the socket.""" + return self.ntransfercmd(cmd, rest)[0] + + def login(self, user = '', passwd = '', acct = ''): + '''Login, default anonymous.''' + if not user: user = 'anonymous' + if not passwd: passwd = '' + if not acct: acct = '' + if user == 'anonymous' and passwd in ('', '-'): + # If there is no anonymous ftp password specified + # then we'll just use anonymous@ + # We don't send any other thing because: + # - We want to remain anonymous + # - We want to stop SPAM + # - We don't want to let ftp sites to discriminate by the user, + # host or country. + passwd = passwd + 'anonymous@' + resp = self.sendcmd('USER ' + user) + if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd) + if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct) + if resp[0] != '2': + raise error_reply, resp + return resp + + def retrbinary(self, cmd, callback, blocksize=8192, rest=None): + """Retrieve data in binary mode. A new port is created for you. + + Args: + cmd: A RETR command. + callback: A single parameter callable to be called on each + block of data read. + blocksize: The maximum number of bytes to read from the + socket at one time. [default: 8192] + rest: Passed to transfercmd(). [default: None] + + Returns: + The response code. + """ + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + while 1: + data = conn.recv(blocksize) + if not data: + break + callback(data) + conn.close() + return self.voidresp() + + def retrlines(self, cmd, callback = None): + """Retrieve data in line mode. A new port is created for you. + + Args: + cmd: A RETR, LIST, NLST, or MLSD command. + callback: An optional single parameter callable that is called + for each line with the trailing CRLF stripped. + [default: print_line()] + + Returns: + The response code. + """ + if callback is None: callback = print_line + resp = self.sendcmd('TYPE A') + conn = self.transfercmd(cmd) + fp = conn.makefile('rb') + while 1: + line = fp.readline() + if self.debugging > 2: print '*retr*', repr(line) + if not line: + break + if line[-2:] == CRLF: + line = line[:-2] + elif line[-1:] == '\n': + line = line[:-1] + callback(line) + fp.close() + conn.close() + return self.voidresp() + + def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): + """Store a file in binary mode. A new port is created for you. + + Args: + cmd: A STOR command. + fp: A file-like object with a read(num_bytes) method. + blocksize: The maximum data size to read from fp and send over + the connection at once. [default: 8192] + callback: An optional single parameter callable that is called on + on each block of data after it is sent. [default: None] + rest: Passed to transfercmd(). [default: None] + + Returns: + The response code. + """ + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + while 1: + buf = fp.read(blocksize) + if not buf: break + conn.sendall(buf) + if callback: callback(buf) + conn.close() + return self.voidresp() + + def storlines(self, cmd, fp, callback=None): + """Store a file in line mode. A new port is created for you. + + Args: + cmd: A STOR command. + fp: A file-like object with a readline() method. + callback: An optional single parameter callable that is called on + on each line after it is sent. [default: None] + + Returns: + The response code. + """ + self.voidcmd('TYPE A') + conn = self.transfercmd(cmd) + while 1: + buf = fp.readline() + if not buf: break + if buf[-2:] != CRLF: + if buf[-1] in CRLF: buf = buf[:-1] + buf = buf + CRLF + conn.sendall(buf) + if callback: callback(buf) + conn.close() + return self.voidresp() + + def acct(self, password): + '''Send new account name.''' + cmd = 'ACCT ' + password + return self.voidcmd(cmd) + + def nlst(self, *args): + '''Return a list of files in a given directory (default the current).''' + cmd = 'NLST' + for arg in args: + cmd = cmd + (' ' + arg) + files = [] + self.retrlines(cmd, files.append) + return files + + def dir(self, *args): + '''List a directory in long form. + By default list current directory to stdout. + Optional last argument is callback function; all + non-empty arguments before it are concatenated to the + LIST command. (This *should* only be used for a pathname.)''' + cmd = 'LIST' + func = None + if args[-1:] and type(args[-1]) != type(''): + args, func = args[:-1], args[-1] + for arg in args: + if arg: + cmd = cmd + (' ' + arg) + self.retrlines(cmd, func) + + def rename(self, fromname, toname): + '''Rename a file.''' + resp = self.sendcmd('RNFR ' + fromname) + if resp[0] != '3': + raise error_reply, resp + return self.voidcmd('RNTO ' + toname) + + def delete(self, filename): + '''Delete a file.''' + resp = self.sendcmd('DELE ' + filename) + if resp[:3] in ('250', '200'): + return resp + else: + raise error_reply, resp + + def cwd(self, dirname): + '''Change to a directory.''' + if dirname == '..': + try: + return self.voidcmd('CDUP') + except error_perm, msg: + if msg.args[0][:3] != '500': + raise + elif dirname == '': + dirname = '.' # does nothing, but could return error + cmd = 'CWD ' + dirname + return self.voidcmd(cmd) + + def size(self, filename): + '''Retrieve the size of a file.''' + # The SIZE command is defined in RFC-3659 + resp = self.sendcmd('SIZE ' + filename) + if resp[:3] == '213': + s = resp[3:].strip() + try: + return int(s) + except (OverflowError, ValueError): + return long(s) + + def mkd(self, dirname): + '''Make a directory, return its full pathname.''' + resp = self.sendcmd('MKD ' + dirname) + return parse257(resp) + + def rmd(self, dirname): + '''Remove a directory.''' + return self.voidcmd('RMD ' + dirname) + + def pwd(self): + '''Return current working directory.''' + resp = self.sendcmd('PWD') + return parse257(resp) + + def quit(self): + '''Quit, and close the connection.''' + resp = self.voidcmd('QUIT') + self.close() + return resp + + def close(self): + '''Close the connection without assuming anything about it.''' + if self.file is not None: + self.file.close() + if self.sock is not None: + self.sock.close() + self.file = self.sock = None + +try: + import ssl +except ImportError: + pass +else: + class FTP_TLS(FTP): + '''A FTP subclass which adds TLS support to FTP as described + in RFC-4217. + + Connect as usual to port 21 implicitly securing the FTP control + connection before authenticating. + + Securing the data connection requires user to explicitly ask + for it by calling prot_p() method. + + Usage example: + >>> from ftplib import FTP_TLS + >>> ftps = FTP_TLS('ftp.python.org') + >>> ftps.login() # login anonymously previously securing control channel + '230 Guest login ok, access restrictions apply.' + >>> ftps.prot_p() # switch to secure data connection + '200 Protection level set to P' + >>> ftps.retrlines('LIST') # list directory content securely + total 9 + drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . + drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. + drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin + drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc + d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming + drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib + drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub + drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr + -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg + '226 Transfer complete.' + >>> ftps.quit() + '221 Goodbye.' + >>> + ''' + ssl_version = ssl.PROTOCOL_TLSv1 + + def __init__(self, host='', user='', passwd='', acct='', keyfile=None, + certfile=None, timeout=_GLOBAL_DEFAULT_TIMEOUT): + self.keyfile = keyfile + self.certfile = certfile + self._prot_p = False + FTP.__init__(self, host, user, passwd, acct, timeout) + + def login(self, user='', passwd='', acct='', secure=True): + if secure and not isinstance(self.sock, ssl.SSLSocket): + self.auth() + return FTP.login(self, user, passwd, acct) + + def auth(self): + '''Set up secure control connection by using TLS/SSL.''' + if isinstance(self.sock, ssl.SSLSocket): + raise ValueError("Already using TLS") + if self.ssl_version == ssl.PROTOCOL_TLSv1: + resp = self.voidcmd('AUTH TLS') + else: + resp = self.voidcmd('AUTH SSL') + self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, + ssl_version=self.ssl_version) + self.file = self.sock.makefile(mode='rb') + return resp + + def prot_p(self): + '''Set up secure data connection.''' + # PROT defines whether or not the data channel is to be protected. + # Though RFC-2228 defines four possible protection levels, + # RFC-4217 only recommends two, Clear and Private. + # Clear (PROT C) means that no security is to be used on the + # data-channel, Private (PROT P) means that the data-channel + # should be protected by TLS. + # PBSZ command MUST still be issued, but must have a parameter of + # '0' to indicate that no buffering is taking place and the data + # connection should not be encapsulated. + self.voidcmd('PBSZ 0') + resp = self.voidcmd('PROT P') + self._prot_p = True + return resp + + def prot_c(self): + '''Set up clear text data connection.''' + resp = self.voidcmd('PROT C') + self._prot_p = False + return resp + + # --- Overridden FTP methods + + def ntransfercmd(self, cmd, rest=None): + conn, size = FTP.ntransfercmd(self, cmd, rest) + if self._prot_p: + conn = ssl.wrap_socket(conn, self.keyfile, self.certfile, + ssl_version=self.ssl_version) + return conn, size + + def retrbinary(self, cmd, callback, blocksize=8192, rest=None): + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + try: + while 1: + data = conn.recv(blocksize) + if not data: + break + callback(data) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + conn.close() + return self.voidresp() + + def retrlines(self, cmd, callback = None): + if callback is None: callback = print_line + resp = self.sendcmd('TYPE A') + conn = self.transfercmd(cmd) + fp = conn.makefile('rb') + try: + while 1: + line = fp.readline() + if self.debugging > 2: print '*retr*', repr(line) + if not line: + break + if line[-2:] == CRLF: + line = line[:-2] + elif line[-1:] == '\n': + line = line[:-1] + callback(line) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + fp.close() + conn.close() + return self.voidresp() + + def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + try: + while 1: + buf = fp.read(blocksize) + if not buf: break + conn.sendall(buf) + if callback: callback(buf) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + conn.close() + return self.voidresp() + + def storlines(self, cmd, fp, callback=None): + self.voidcmd('TYPE A') + conn = self.transfercmd(cmd) + try: + while 1: + buf = fp.readline() + if not buf: break + if buf[-2:] != CRLF: + if buf[-1] in CRLF: buf = buf[:-1] + buf = buf + CRLF + conn.sendall(buf) + if callback: callback(buf) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + conn.close() + return self.voidresp() + + __all__.append('FTP_TLS') + all_errors = (Error, IOError, EOFError, ssl.SSLError) + + +_150_re = None + +def parse150(resp): + '''Parse the '150' response for a RETR request. + Returns the expected transfer size or None; size is not guaranteed to + be present in the 150 message. + ''' + if resp[:3] != '150': + raise error_reply, resp + global _150_re + if _150_re is None: + import re + _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE) + m = _150_re.match(resp) + if not m: + return None + s = m.group(1) + try: + return int(s) + except (OverflowError, ValueError): + return long(s) + + +_227_re = None + +def parse227(resp): + '''Parse the '227' response for a PASV request. + Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)' + Return ('host.addr.as.numbers', port#) tuple.''' + + if resp[:3] != '227': + raise error_reply, resp + global _227_re + if _227_re is None: + import re + _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)') + m = _227_re.search(resp) + if not m: + raise error_proto, resp + numbers = m.groups() + host = '.'.join(numbers[:4]) + port = (int(numbers[4]) << 8) + int(numbers[5]) + return host, port + + +def parse229(resp, peer): + '''Parse the '229' response for a EPSV request. + Raises error_proto if it does not contain '(|||port|)' + Return ('host.addr.as.numbers', port#) tuple.''' + + if resp[:3] != '229': + raise error_reply, resp + left = resp.find('(') + if left < 0: raise error_proto, resp + right = resp.find(')', left + 1) + if right < 0: + raise error_proto, resp # should contain '(|||port|)' + if resp[left + 1] != resp[right - 1]: + raise error_proto, resp + parts = resp[left + 1:right].split(resp[left+1]) + if len(parts) != 5: + raise error_proto, resp + host = peer[0] + port = int(parts[3]) + return host, port + + +def parse257(resp): + '''Parse the '257' response for a MKD or PWD request. + This is a response to a MKD or PWD request: a directory name. + Returns the directoryname in the 257 reply.''' + + if resp[:3] != '257': + raise error_reply, resp + if resp[3:5] != ' "': + return '' # Not compliant to RFC 959, but UNIX ftpd does this + dirname = '' + i = 5 + n = len(resp) + while i < n: + c = resp[i] + i = i+1 + if c == '"': + if i >= n or resp[i] != '"': + break + i = i+1 + dirname = dirname + c + return dirname + + +def print_line(line): + '''Default retrlines callback to print a line.''' + print line + + +def ftpcp(source, sourcename, target, targetname = '', type = 'I'): + '''Copy file from one FTP-instance to another.''' + if not targetname: targetname = sourcename + type = 'TYPE ' + type + source.voidcmd(type) + target.voidcmd(type) + sourcehost, sourceport = parse227(source.sendcmd('PASV')) + target.sendport(sourcehost, sourceport) + # RFC 959: the user must "listen" [...] BEFORE sending the + # transfer request. + # So: STOR before RETR, because here the target is a "user". + treply = target.sendcmd('STOR ' + targetname) + if treply[:3] not in ('125', '150'): raise error_proto # RFC 959 + sreply = source.sendcmd('RETR ' + sourcename) + if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959 + source.voidresp() + target.voidresp() + + +class Netrc: + """Class to parse & provide access to 'netrc' format files. + + See the netrc(4) man page for information on the file format. + + WARNING: This class is obsolete -- use module netrc instead. + + """ + __defuser = None + __defpasswd = None + __defacct = None + + def __init__(self, filename=None): + if filename is None: + if "HOME" in os.environ: + filename = os.path.join(os.environ["HOME"], + ".netrc") + else: + raise IOError, \ + "specify file to load or set $HOME" + self.__hosts = {} + self.__macros = {} + fp = open(filename, "r") + in_macro = 0 + while 1: + line = fp.readline() + if not line: break + if in_macro and line.strip(): + macro_lines.append(line) + continue + elif in_macro: + self.__macros[macro_name] = tuple(macro_lines) + in_macro = 0 + words = line.split() + host = user = passwd = acct = None + default = 0 + i = 0 + while i < len(words): + w1 = words[i] + if i+1 < len(words): + w2 = words[i + 1] + else: + w2 = None + if w1 == 'default': + default = 1 + elif w1 == 'machine' and w2: + host = w2.lower() + i = i + 1 + elif w1 == 'login' and w2: + user = w2 + i = i + 1 + elif w1 == 'password' and w2: + passwd = w2 + i = i + 1 + elif w1 == 'account' and w2: + acct = w2 + i = i + 1 + elif w1 == 'macdef' and w2: + macro_name = w2 + macro_lines = [] + in_macro = 1 + break + i = i + 1 + if default: + self.__defuser = user or self.__defuser + self.__defpasswd = passwd or self.__defpasswd + self.__defacct = acct or self.__defacct + if host: + if host in self.__hosts: + ouser, opasswd, oacct = \ + self.__hosts[host] + user = user or ouser + passwd = passwd or opasswd + acct = acct or oacct + self.__hosts[host] = user, passwd, acct + fp.close() + + def get_hosts(self): + """Return a list of hosts mentioned in the .netrc file.""" + return self.__hosts.keys() + + def get_account(self, host): + """Returns login information for the named host. + + The return value is a triple containing userid, + password, and the accounting field. + + """ + host = host.lower() + user = passwd = acct = None + if host in self.__hosts: + user, passwd, acct = self.__hosts[host] + user = user or self.__defuser + passwd = passwd or self.__defpasswd + acct = acct or self.__defacct + return user, passwd, acct + + def get_macros(self): + """Return a list of all defined macro names.""" + return self.__macros.keys() + + def get_macro(self, macro): + """Return a sequence of lines which define a named macro.""" + return self.__macros[macro] + + + +def test(): + '''Test program. + Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ... + + -d dir + -l list + -p password + ''' + + if len(sys.argv) < 2: + print test.__doc__ + sys.exit(0) + + debugging = 0 + rcfile = None + while sys.argv[1] == '-d': + debugging = debugging+1 + del sys.argv[1] + if sys.argv[1][:2] == '-r': + # get name of alternate ~/.netrc file: + rcfile = sys.argv[1][2:] + del sys.argv[1] + host = sys.argv[1] + ftp = FTP(host) + ftp.set_debuglevel(debugging) + userid = passwd = acct = '' + try: + netrc = Netrc(rcfile) + except IOError: + if rcfile is not None: + sys.stderr.write("Could not open account file" + " -- using anonymous login.") + else: + try: + userid, passwd, acct = netrc.get_account(host) + except KeyError: + # no account for host + sys.stderr.write( + "No account -- using anonymous login.") + ftp.login(userid, passwd, acct) + for file in sys.argv[2:]: + if file[:2] == '-l': + ftp.dir(file[2:]) + elif file[:2] == '-d': + cmd = 'CWD' + if file[2:]: cmd = cmd + ' ' + file[2:] + resp = ftp.sendcmd(cmd) + elif file == '-p': + ftp.set_pasv(not ftp.passiveserver) + else: + ftp.retrbinary('RETR ' + file, \ + sys.stdout.write, 1024) + ftp.quit() + + +if __name__ == '__main__': + test() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 3 15:01:47 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 3 Feb 2013 15:01:47 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fixing_SSL_treatment_on_jyt?= =?utf-8?q?hon=2C_re-enabling_related_tests?= Message-ID: <3YzYd75tBSzPx0@mail.python.org> http://hg.python.org/jython/rev/c52aac19c12e changeset: 6982:c52aac19c12e user: Alan Kennedy date: Sun Feb 03 13:59:04 2013 +0000 summary: Fixing SSL treatment on jython, re-enabling related tests files: Lib/ftplib.py | 3 ++- Lib/test/test_urllib.py | 1 - Lib/test/test_urllib2.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/ftplib.py b/Lib/ftplib.py --- a/Lib/ftplib.py +++ b/Lib/ftplib.py @@ -593,7 +593,8 @@ try: import ssl -except ImportError: + ssl.PROTOCOL_TLSv1 +except (ImportError, AttributeError): pass else: class FTP_TLS(FTP): diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -240,7 +240,6 @@ self.assertFalse(os.path.exists(tmp_file)) self.assertRaises(IOError, urllib.urlopen, tmp_fileurl) - @unittest.skipIf(test_support.is_jython, "Required SSL support not yet available on jython") def test_ftp_nonexisting(self): self.assertRaises(IOError, urllib.urlopen, 'ftp://localhost/not/existing/file.py') diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -605,7 +605,6 @@ class HandlerTests(unittest.TestCase): - @unittest.skipIf(test_support.is_jython, "Required SSL support not yet available on jython") def test_ftp(self): class MockFTPWrapper: def __init__(self, data): self.data = data -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 18:32:58 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 18:32:58 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_jaffl_no_longer_needed=2E?= Message-ID: <3Z0GGL3xD8zSc6@mail.python.org> http://hg.python.org/jython/rev/8e8f4aa4f3cb changeset: 6983:8e8f4aa4f3cb parent: 6974:a113ab4287fc user: Frank Wierzbicki date: Mon Feb 04 09:31:58 2013 -0800 summary: jaffl no longer needed. files: .classpath | 1 - .idea/libraries/extlibs.xml | 1 - .idea/libraries/extlibs2.xml | 1 - b/.idea/libraries/extlibs.xml | 1 - build.xml | 2 -- extlibs/jaffl.jar | Bin 6 files changed, 0 insertions(+), 6 deletions(-) diff --git a/.classpath b/.classpath --- a/.classpath +++ b/.classpath @@ -21,7 +21,6 @@ - diff --git a/.idea/libraries/extlibs.xml b/.idea/libraries/extlibs.xml --- a/.idea/libraries/extlibs.xml +++ b/.idea/libraries/extlibs.xml @@ -19,7 +19,6 @@ - diff --git a/.idea/libraries/extlibs2.xml b/.idea/libraries/extlibs2.xml --- a/.idea/libraries/extlibs2.xml +++ b/.idea/libraries/extlibs2.xml @@ -22,7 +22,6 @@ - diff --git a/b/.idea/libraries/extlibs.xml b/b/.idea/libraries/extlibs.xml --- a/b/.idea/libraries/extlibs.xml +++ b/b/.idea/libraries/extlibs.xml @@ -22,7 +22,6 @@ - diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -199,7 +199,6 @@ - @@ -645,7 +644,6 @@ - diff --git a/extlibs/jaffl.jar b/extlibs/jaffl.jar deleted file mode 100644 Binary file extlibs/jaffl.jar has changed -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 18:33:00 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 18:33:00 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?b?KTogTWVyZ2Uu?= Message-ID: <3Z0GGN5c0HzSc9@mail.python.org> http://hg.python.org/jython/rev/73cc6455b2e1 changeset: 6984:73cc6455b2e1 parent: 6983:8e8f4aa4f3cb parent: 6982:c52aac19c12e user: Frank Wierzbicki date: Mon Feb 04 09:32:36 2013 -0800 summary: Merge. files: Lib/ftplib.py | 1047 +++++++++++++++++ Lib/socket.py | 43 +- Lib/ssl.py | 2 +- Lib/test/test_socket_ssl.py | 9 + Lib/test/test_urllib.py | 249 +++- Lib/test/test_urllib2.py | 103 +- Lib/test/test_urllib2_localnet.py | 343 ++++- Lib/urllib.py | 232 +-- 8 files changed, 1781 insertions(+), 247 deletions(-) diff --git a/Lib/ftplib.py b/Lib/ftplib.py new file mode 100644 --- /dev/null +++ b/Lib/ftplib.py @@ -0,0 +1,1047 @@ +"""An FTP client class and some helper functions. + +Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds + +Example: + +>>> from ftplib import FTP +>>> ftp = FTP('ftp.python.org') # connect to host, default port +>>> ftp.login() # default, i.e.: user anonymous, passwd anonymous@ +'230 Guest login ok, access restrictions apply.' +>>> ftp.retrlines('LIST') # list directory contents +total 9 +drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . +drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. +drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin +drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc +d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming +drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib +drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub +drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr +-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg +'226 Transfer complete.' +>>> ftp.quit() +'221 Goodbye.' +>>> + +A nice test that reveals some of the network dialogue would be: +python ftplib.py -d localhost -l -p -l +""" + +# +# Changes and improvements suggested by Steve Majewski. +# Modified by Jack to work on the mac. +# Modified by Siebren to support docstrings and PASV. +# Modified by Phil Schwartz to add storbinary and storlines callbacks. +# Modified by Giampaolo Rodola' to add TLS support. +# + +import os +import sys + +# Import SOCKS module if it exists, else standard socket module socket +try: + import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket + from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn +except ImportError: + import socket +from socket import _GLOBAL_DEFAULT_TIMEOUT + +__all__ = ["FTP","Netrc"] + +# Magic number from +MSG_OOB = 0x1 # Process data out of band + + +# The standard FTP server control port +FTP_PORT = 21 + + +# Exception raised when an error or invalid response is received +class Error(Exception): pass +class error_reply(Error): pass # unexpected [123]xx reply +class error_temp(Error): pass # 4xx errors +class error_perm(Error): pass # 5xx errors +class error_proto(Error): pass # response does not begin with [1-5] + + +# All exceptions (hopefully) that may be raised here and that aren't +# (always) programming errors on our side +all_errors = (Error, IOError, EOFError) + + +# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) +CRLF = '\r\n' + +# The class itself +class FTP: + + '''An FTP client class. + + To create a connection, call the class using these arguments: + host, user, passwd, acct, timeout + + The first four arguments are all strings, and have default value ''. + timeout must be numeric and defaults to None if not passed, + meaning that no timeout will be set on any ftp socket(s) + If a timeout is passed, then this is now the default timeout for all ftp + socket operations for this instance. + + Then use self.connect() with optional host and port argument. + + To download a file, use ftp.retrlines('RETR ' + filename), + or ftp.retrbinary() with slightly different arguments. + To upload a file, use ftp.storlines() or ftp.storbinary(), + which have an open file as argument (see their definitions + below for details). + The download/upload functions first issue appropriate TYPE + and PORT or PASV commands. +''' + + debugging = 0 + host = '' + port = FTP_PORT + sock = None + file = None + welcome = None + passiveserver = 1 + + # Initialization method (called by class instantiation). + # Initialize host to localhost, port to standard ftp port + # Optional arguments are host (for connect()), + # and user, passwd, acct (for login()) + def __init__(self, host='', user='', passwd='', acct='', + timeout=_GLOBAL_DEFAULT_TIMEOUT): + self.timeout = timeout + if host: + self.connect(host) + if user: + self.login(user, passwd, acct) + + def connect(self, host='', port=0, timeout=-999): + '''Connect to host. Arguments are: + - host: hostname to connect to (string, default previous host) + - port: port to connect to (integer, default previous port) + ''' + if host != '': + self.host = host + if port > 0: + self.port = port + if timeout != -999: + self.timeout = timeout + self.sock = socket.create_connection((self.host, self.port), self.timeout) + self.af = self.sock.family + self.file = self.sock.makefile('rb') + self.welcome = self.getresp() + return self.welcome + + def getwelcome(self): + '''Get the welcome message from the server. + (this is read and squirreled away by connect())''' + if self.debugging: + print '*welcome*', self.sanitize(self.welcome) + return self.welcome + + def set_debuglevel(self, level): + '''Set the debugging level. + The required argument level means: + 0: no debugging output (default) + 1: print commands and responses but not body text etc. + 2: also print raw lines read and sent before stripping CR/LF''' + self.debugging = level + debug = set_debuglevel + + def set_pasv(self, val): + '''Use passive or active mode for data transfers. + With a false argument, use the normal PORT mode, + With a true argument, use the PASV command.''' + self.passiveserver = val + + # Internal: "sanitize" a string for printing + def sanitize(self, s): + if s[:5] == 'pass ' or s[:5] == 'PASS ': + i = len(s) + while i > 5 and s[i-1] in '\r\n': + i = i-1 + s = s[:5] + '*'*(i-5) + s[i:] + return repr(s) + + # Internal: send one line to the server, appending CRLF + def putline(self, line): + line = line + CRLF + if self.debugging > 1: print '*put*', self.sanitize(line) + self.sock.sendall(line) + + # Internal: send one command to the server (through putline()) + def putcmd(self, line): + if self.debugging: print '*cmd*', self.sanitize(line) + self.putline(line) + + # Internal: return one line from the server, stripping CRLF. + # Raise EOFError if the connection is closed + def getline(self): + line = self.file.readline() + if self.debugging > 1: + print '*get*', self.sanitize(line) + if not line: raise EOFError + if line[-2:] == CRLF: line = line[:-2] + elif line[-1:] in CRLF: line = line[:-1] + return line + + # Internal: get a response from the server, which may possibly + # consist of multiple lines. Return a single string with no + # trailing CRLF. If the response consists of multiple lines, + # these are separated by '\n' characters in the string + def getmultiline(self): + line = self.getline() + if line[3:4] == '-': + code = line[:3] + while 1: + nextline = self.getline() + line = line + ('\n' + nextline) + if nextline[:3] == code and \ + nextline[3:4] != '-': + break + return line + + # Internal: get a response from the server. + # Raise various errors if the response indicates an error + def getresp(self): + resp = self.getmultiline() + if self.debugging: print '*resp*', self.sanitize(resp) + self.lastresp = resp[:3] + c = resp[:1] + if c in ('1', '2', '3'): + return resp + if c == '4': + raise error_temp, resp + if c == '5': + raise error_perm, resp + raise error_proto, resp + + def voidresp(self): + """Expect a response beginning with '2'.""" + resp = self.getresp() + if resp[:1] != '2': + raise error_reply, resp + return resp + + def abort(self): + '''Abort a file transfer. Uses out-of-band data. + This does not follow the procedure from the RFC to send Telnet + IP and Synch; that doesn't seem to work with the servers I've + tried. Instead, just send the ABOR command as OOB data.''' + line = 'ABOR' + CRLF + if self.debugging > 1: print '*put urgent*', self.sanitize(line) + self.sock.sendall(line, MSG_OOB) + resp = self.getmultiline() + if resp[:3] not in ('426', '225', '226'): + raise error_proto, resp + + def sendcmd(self, cmd): + '''Send a command and return the response.''' + self.putcmd(cmd) + return self.getresp() + + def voidcmd(self, cmd): + """Send a command and expect a response beginning with '2'.""" + self.putcmd(cmd) + return self.voidresp() + + def sendport(self, host, port): + '''Send a PORT command with the current host and the given + port number. + ''' + hbytes = host.split('.') + pbytes = [repr(port//256), repr(port%256)] + bytes = hbytes + pbytes + cmd = 'PORT ' + ','.join(bytes) + return self.voidcmd(cmd) + + def sendeprt(self, host, port): + '''Send a EPRT command with the current host and the given port number.''' + af = 0 + if self.af == socket.AF_INET: + af = 1 + if self.af == socket.AF_INET6: + af = 2 + if af == 0: + raise error_proto, 'unsupported address family' + fields = ['', repr(af), host, repr(port), ''] + cmd = 'EPRT ' + '|'.join(fields) + return self.voidcmd(cmd) + + def makeport(self): + '''Create a new socket and send a PORT command for it.''' + err = None + sock = None + for res in socket.getaddrinfo(None, 0, self.af, socket.SOCK_STREAM, 0, socket.AI_PASSIVE): + af, socktype, proto, canonname, sa = res + try: + sock = socket.socket(af, socktype, proto) + sock.bind(sa) + except socket.error, err: + if sock: + sock.close() + sock = None + continue + break + if sock is None: + if err is not None: + raise err + else: + raise socket.error("getaddrinfo returns an empty list") + sock.listen(1) + port = sock.getsockname()[1] # Get proper port + host = self.sock.getsockname()[0] # Get proper host + if self.af == socket.AF_INET: + resp = self.sendport(host, port) + else: + resp = self.sendeprt(host, port) + if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(self.timeout) + return sock + + def makepasv(self): + if self.af == socket.AF_INET: + host, port = parse227(self.sendcmd('PASV')) + else: + host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername()) + return host, port + + def ntransfercmd(self, cmd, rest=None): + """Initiate a transfer over the data connection. + + If the transfer is active, send a port command and the + transfer command, and accept the connection. If the server is + passive, send a pasv command, connect to it, and start the + transfer command. Either way, return the socket for the + connection and the expected size of the transfer. The + expected size may be None if it could not be determined. + + Optional `rest' argument can be a string that is sent as the + argument to a REST command. This is essentially a server + marker used to tell the server to skip over any data up to the + given marker. + """ + size = None + if self.passiveserver: + host, port = self.makepasv() + conn = socket.create_connection((host, port), self.timeout) + try: + if rest is not None: + self.sendcmd("REST %s" % rest) + resp = self.sendcmd(cmd) + # Some servers apparently send a 200 reply to + # a LIST or STOR command, before the 150 reply + # (and way before the 226 reply). This seems to + # be in violation of the protocol (which only allows + # 1xx or error messages for LIST), so we just discard + # this response. + if resp[0] == '2': + resp = self.getresp() + if resp[0] != '1': + raise error_reply, resp + except: + conn.close() + raise + else: + sock = self.makeport() + try: + if rest is not None: + self.sendcmd("REST %s" % rest) + resp = self.sendcmd(cmd) + # See above. + if resp[0] == '2': + resp = self.getresp() + if resp[0] != '1': + raise error_reply, resp + conn, sockaddr = sock.accept() + if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT: + conn.settimeout(self.timeout) + finally: + sock.close() + if resp[:3] == '150': + # this is conditional in case we received a 125 + size = parse150(resp) + return conn, size + + def transfercmd(self, cmd, rest=None): + """Like ntransfercmd() but returns only the socket.""" + return self.ntransfercmd(cmd, rest)[0] + + def login(self, user = '', passwd = '', acct = ''): + '''Login, default anonymous.''' + if not user: user = 'anonymous' + if not passwd: passwd = '' + if not acct: acct = '' + if user == 'anonymous' and passwd in ('', '-'): + # If there is no anonymous ftp password specified + # then we'll just use anonymous@ + # We don't send any other thing because: + # - We want to remain anonymous + # - We want to stop SPAM + # - We don't want to let ftp sites to discriminate by the user, + # host or country. + passwd = passwd + 'anonymous@' + resp = self.sendcmd('USER ' + user) + if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd) + if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct) + if resp[0] != '2': + raise error_reply, resp + return resp + + def retrbinary(self, cmd, callback, blocksize=8192, rest=None): + """Retrieve data in binary mode. A new port is created for you. + + Args: + cmd: A RETR command. + callback: A single parameter callable to be called on each + block of data read. + blocksize: The maximum number of bytes to read from the + socket at one time. [default: 8192] + rest: Passed to transfercmd(). [default: None] + + Returns: + The response code. + """ + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + while 1: + data = conn.recv(blocksize) + if not data: + break + callback(data) + conn.close() + return self.voidresp() + + def retrlines(self, cmd, callback = None): + """Retrieve data in line mode. A new port is created for you. + + Args: + cmd: A RETR, LIST, NLST, or MLSD command. + callback: An optional single parameter callable that is called + for each line with the trailing CRLF stripped. + [default: print_line()] + + Returns: + The response code. + """ + if callback is None: callback = print_line + resp = self.sendcmd('TYPE A') + conn = self.transfercmd(cmd) + fp = conn.makefile('rb') + while 1: + line = fp.readline() + if self.debugging > 2: print '*retr*', repr(line) + if not line: + break + if line[-2:] == CRLF: + line = line[:-2] + elif line[-1:] == '\n': + line = line[:-1] + callback(line) + fp.close() + conn.close() + return self.voidresp() + + def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): + """Store a file in binary mode. A new port is created for you. + + Args: + cmd: A STOR command. + fp: A file-like object with a read(num_bytes) method. + blocksize: The maximum data size to read from fp and send over + the connection at once. [default: 8192] + callback: An optional single parameter callable that is called on + on each block of data after it is sent. [default: None] + rest: Passed to transfercmd(). [default: None] + + Returns: + The response code. + """ + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + while 1: + buf = fp.read(blocksize) + if not buf: break + conn.sendall(buf) + if callback: callback(buf) + conn.close() + return self.voidresp() + + def storlines(self, cmd, fp, callback=None): + """Store a file in line mode. A new port is created for you. + + Args: + cmd: A STOR command. + fp: A file-like object with a readline() method. + callback: An optional single parameter callable that is called on + on each line after it is sent. [default: None] + + Returns: + The response code. + """ + self.voidcmd('TYPE A') + conn = self.transfercmd(cmd) + while 1: + buf = fp.readline() + if not buf: break + if buf[-2:] != CRLF: + if buf[-1] in CRLF: buf = buf[:-1] + buf = buf + CRLF + conn.sendall(buf) + if callback: callback(buf) + conn.close() + return self.voidresp() + + def acct(self, password): + '''Send new account name.''' + cmd = 'ACCT ' + password + return self.voidcmd(cmd) + + def nlst(self, *args): + '''Return a list of files in a given directory (default the current).''' + cmd = 'NLST' + for arg in args: + cmd = cmd + (' ' + arg) + files = [] + self.retrlines(cmd, files.append) + return files + + def dir(self, *args): + '''List a directory in long form. + By default list current directory to stdout. + Optional last argument is callback function; all + non-empty arguments before it are concatenated to the + LIST command. (This *should* only be used for a pathname.)''' + cmd = 'LIST' + func = None + if args[-1:] and type(args[-1]) != type(''): + args, func = args[:-1], args[-1] + for arg in args: + if arg: + cmd = cmd + (' ' + arg) + self.retrlines(cmd, func) + + def rename(self, fromname, toname): + '''Rename a file.''' + resp = self.sendcmd('RNFR ' + fromname) + if resp[0] != '3': + raise error_reply, resp + return self.voidcmd('RNTO ' + toname) + + def delete(self, filename): + '''Delete a file.''' + resp = self.sendcmd('DELE ' + filename) + if resp[:3] in ('250', '200'): + return resp + else: + raise error_reply, resp + + def cwd(self, dirname): + '''Change to a directory.''' + if dirname == '..': + try: + return self.voidcmd('CDUP') + except error_perm, msg: + if msg.args[0][:3] != '500': + raise + elif dirname == '': + dirname = '.' # does nothing, but could return error + cmd = 'CWD ' + dirname + return self.voidcmd(cmd) + + def size(self, filename): + '''Retrieve the size of a file.''' + # The SIZE command is defined in RFC-3659 + resp = self.sendcmd('SIZE ' + filename) + if resp[:3] == '213': + s = resp[3:].strip() + try: + return int(s) + except (OverflowError, ValueError): + return long(s) + + def mkd(self, dirname): + '''Make a directory, return its full pathname.''' + resp = self.sendcmd('MKD ' + dirname) + return parse257(resp) + + def rmd(self, dirname): + '''Remove a directory.''' + return self.voidcmd('RMD ' + dirname) + + def pwd(self): + '''Return current working directory.''' + resp = self.sendcmd('PWD') + return parse257(resp) + + def quit(self): + '''Quit, and close the connection.''' + resp = self.voidcmd('QUIT') + self.close() + return resp + + def close(self): + '''Close the connection without assuming anything about it.''' + if self.file is not None: + self.file.close() + if self.sock is not None: + self.sock.close() + self.file = self.sock = None + +try: + import ssl + ssl.PROTOCOL_TLSv1 +except (ImportError, AttributeError): + pass +else: + class FTP_TLS(FTP): + '''A FTP subclass which adds TLS support to FTP as described + in RFC-4217. + + Connect as usual to port 21 implicitly securing the FTP control + connection before authenticating. + + Securing the data connection requires user to explicitly ask + for it by calling prot_p() method. + + Usage example: + >>> from ftplib import FTP_TLS + >>> ftps = FTP_TLS('ftp.python.org') + >>> ftps.login() # login anonymously previously securing control channel + '230 Guest login ok, access restrictions apply.' + >>> ftps.prot_p() # switch to secure data connection + '200 Protection level set to P' + >>> ftps.retrlines('LIST') # list directory content securely + total 9 + drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . + drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. + drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin + drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc + d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming + drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib + drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub + drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr + -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg + '226 Transfer complete.' + >>> ftps.quit() + '221 Goodbye.' + >>> + ''' + ssl_version = ssl.PROTOCOL_TLSv1 + + def __init__(self, host='', user='', passwd='', acct='', keyfile=None, + certfile=None, timeout=_GLOBAL_DEFAULT_TIMEOUT): + self.keyfile = keyfile + self.certfile = certfile + self._prot_p = False + FTP.__init__(self, host, user, passwd, acct, timeout) + + def login(self, user='', passwd='', acct='', secure=True): + if secure and not isinstance(self.sock, ssl.SSLSocket): + self.auth() + return FTP.login(self, user, passwd, acct) + + def auth(self): + '''Set up secure control connection by using TLS/SSL.''' + if isinstance(self.sock, ssl.SSLSocket): + raise ValueError("Already using TLS") + if self.ssl_version == ssl.PROTOCOL_TLSv1: + resp = self.voidcmd('AUTH TLS') + else: + resp = self.voidcmd('AUTH SSL') + self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, + ssl_version=self.ssl_version) + self.file = self.sock.makefile(mode='rb') + return resp + + def prot_p(self): + '''Set up secure data connection.''' + # PROT defines whether or not the data channel is to be protected. + # Though RFC-2228 defines four possible protection levels, + # RFC-4217 only recommends two, Clear and Private. + # Clear (PROT C) means that no security is to be used on the + # data-channel, Private (PROT P) means that the data-channel + # should be protected by TLS. + # PBSZ command MUST still be issued, but must have a parameter of + # '0' to indicate that no buffering is taking place and the data + # connection should not be encapsulated. + self.voidcmd('PBSZ 0') + resp = self.voidcmd('PROT P') + self._prot_p = True + return resp + + def prot_c(self): + '''Set up clear text data connection.''' + resp = self.voidcmd('PROT C') + self._prot_p = False + return resp + + # --- Overridden FTP methods + + def ntransfercmd(self, cmd, rest=None): + conn, size = FTP.ntransfercmd(self, cmd, rest) + if self._prot_p: + conn = ssl.wrap_socket(conn, self.keyfile, self.certfile, + ssl_version=self.ssl_version) + return conn, size + + def retrbinary(self, cmd, callback, blocksize=8192, rest=None): + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + try: + while 1: + data = conn.recv(blocksize) + if not data: + break + callback(data) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + conn.close() + return self.voidresp() + + def retrlines(self, cmd, callback = None): + if callback is None: callback = print_line + resp = self.sendcmd('TYPE A') + conn = self.transfercmd(cmd) + fp = conn.makefile('rb') + try: + while 1: + line = fp.readline() + if self.debugging > 2: print '*retr*', repr(line) + if not line: + break + if line[-2:] == CRLF: + line = line[:-2] + elif line[-1:] == '\n': + line = line[:-1] + callback(line) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + fp.close() + conn.close() + return self.voidresp() + + def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): + self.voidcmd('TYPE I') + conn = self.transfercmd(cmd, rest) + try: + while 1: + buf = fp.read(blocksize) + if not buf: break + conn.sendall(buf) + if callback: callback(buf) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + conn.close() + return self.voidresp() + + def storlines(self, cmd, fp, callback=None): + self.voidcmd('TYPE A') + conn = self.transfercmd(cmd) + try: + while 1: + buf = fp.readline() + if not buf: break + if buf[-2:] != CRLF: + if buf[-1] in CRLF: buf = buf[:-1] + buf = buf + CRLF + conn.sendall(buf) + if callback: callback(buf) + # shutdown ssl layer + if isinstance(conn, ssl.SSLSocket): + conn.unwrap() + finally: + conn.close() + return self.voidresp() + + __all__.append('FTP_TLS') + all_errors = (Error, IOError, EOFError, ssl.SSLError) + + +_150_re = None + +def parse150(resp): + '''Parse the '150' response for a RETR request. + Returns the expected transfer size or None; size is not guaranteed to + be present in the 150 message. + ''' + if resp[:3] != '150': + raise error_reply, resp + global _150_re + if _150_re is None: + import re + _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE) + m = _150_re.match(resp) + if not m: + return None + s = m.group(1) + try: + return int(s) + except (OverflowError, ValueError): + return long(s) + + +_227_re = None + +def parse227(resp): + '''Parse the '227' response for a PASV request. + Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)' + Return ('host.addr.as.numbers', port#) tuple.''' + + if resp[:3] != '227': + raise error_reply, resp + global _227_re + if _227_re is None: + import re + _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)') + m = _227_re.search(resp) + if not m: + raise error_proto, resp + numbers = m.groups() + host = '.'.join(numbers[:4]) + port = (int(numbers[4]) << 8) + int(numbers[5]) + return host, port + + +def parse229(resp, peer): + '''Parse the '229' response for a EPSV request. + Raises error_proto if it does not contain '(|||port|)' + Return ('host.addr.as.numbers', port#) tuple.''' + + if resp[:3] != '229': + raise error_reply, resp + left = resp.find('(') + if left < 0: raise error_proto, resp + right = resp.find(')', left + 1) + if right < 0: + raise error_proto, resp # should contain '(|||port|)' + if resp[left + 1] != resp[right - 1]: + raise error_proto, resp + parts = resp[left + 1:right].split(resp[left+1]) + if len(parts) != 5: + raise error_proto, resp + host = peer[0] + port = int(parts[3]) + return host, port + + +def parse257(resp): + '''Parse the '257' response for a MKD or PWD request. + This is a response to a MKD or PWD request: a directory name. + Returns the directoryname in the 257 reply.''' + + if resp[:3] != '257': + raise error_reply, resp + if resp[3:5] != ' "': + return '' # Not compliant to RFC 959, but UNIX ftpd does this + dirname = '' + i = 5 + n = len(resp) + while i < n: + c = resp[i] + i = i+1 + if c == '"': + if i >= n or resp[i] != '"': + break + i = i+1 + dirname = dirname + c + return dirname + + +def print_line(line): + '''Default retrlines callback to print a line.''' + print line + + +def ftpcp(source, sourcename, target, targetname = '', type = 'I'): + '''Copy file from one FTP-instance to another.''' + if not targetname: targetname = sourcename + type = 'TYPE ' + type + source.voidcmd(type) + target.voidcmd(type) + sourcehost, sourceport = parse227(source.sendcmd('PASV')) + target.sendport(sourcehost, sourceport) + # RFC 959: the user must "listen" [...] BEFORE sending the + # transfer request. + # So: STOR before RETR, because here the target is a "user". + treply = target.sendcmd('STOR ' + targetname) + if treply[:3] not in ('125', '150'): raise error_proto # RFC 959 + sreply = source.sendcmd('RETR ' + sourcename) + if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959 + source.voidresp() + target.voidresp() + + +class Netrc: + """Class to parse & provide access to 'netrc' format files. + + See the netrc(4) man page for information on the file format. + + WARNING: This class is obsolete -- use module netrc instead. + + """ + __defuser = None + __defpasswd = None + __defacct = None + + def __init__(self, filename=None): + if filename is None: + if "HOME" in os.environ: + filename = os.path.join(os.environ["HOME"], + ".netrc") + else: + raise IOError, \ + "specify file to load or set $HOME" + self.__hosts = {} + self.__macros = {} + fp = open(filename, "r") + in_macro = 0 + while 1: + line = fp.readline() + if not line: break + if in_macro and line.strip(): + macro_lines.append(line) + continue + elif in_macro: + self.__macros[macro_name] = tuple(macro_lines) + in_macro = 0 + words = line.split() + host = user = passwd = acct = None + default = 0 + i = 0 + while i < len(words): + w1 = words[i] + if i+1 < len(words): + w2 = words[i + 1] + else: + w2 = None + if w1 == 'default': + default = 1 + elif w1 == 'machine' and w2: + host = w2.lower() + i = i + 1 + elif w1 == 'login' and w2: + user = w2 + i = i + 1 + elif w1 == 'password' and w2: + passwd = w2 + i = i + 1 + elif w1 == 'account' and w2: + acct = w2 + i = i + 1 + elif w1 == 'macdef' and w2: + macro_name = w2 + macro_lines = [] + in_macro = 1 + break + i = i + 1 + if default: + self.__defuser = user or self.__defuser + self.__defpasswd = passwd or self.__defpasswd + self.__defacct = acct or self.__defacct + if host: + if host in self.__hosts: + ouser, opasswd, oacct = \ + self.__hosts[host] + user = user or ouser + passwd = passwd or opasswd + acct = acct or oacct + self.__hosts[host] = user, passwd, acct + fp.close() + + def get_hosts(self): + """Return a list of hosts mentioned in the .netrc file.""" + return self.__hosts.keys() + + def get_account(self, host): + """Returns login information for the named host. + + The return value is a triple containing userid, + password, and the accounting field. + + """ + host = host.lower() + user = passwd = acct = None + if host in self.__hosts: + user, passwd, acct = self.__hosts[host] + user = user or self.__defuser + passwd = passwd or self.__defpasswd + acct = acct or self.__defacct + return user, passwd, acct + + def get_macros(self): + """Return a list of all defined macro names.""" + return self.__macros.keys() + + def get_macro(self, macro): + """Return a sequence of lines which define a named macro.""" + return self.__macros[macro] + + + +def test(): + '''Test program. + Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ... + + -d dir + -l list + -p password + ''' + + if len(sys.argv) < 2: + print test.__doc__ + sys.exit(0) + + debugging = 0 + rcfile = None + while sys.argv[1] == '-d': + debugging = debugging+1 + del sys.argv[1] + if sys.argv[1][:2] == '-r': + # get name of alternate ~/.netrc file: + rcfile = sys.argv[1][2:] + del sys.argv[1] + host = sys.argv[1] + ftp = FTP(host) + ftp.set_debuglevel(debugging) + userid = passwd = acct = '' + try: + netrc = Netrc(rcfile) + except IOError: + if rcfile is not None: + sys.stderr.write("Could not open account file" + " -- using anonymous login.") + else: + try: + userid, passwd, acct = netrc.get_account(host) + except KeyError: + # no account for host + sys.stderr.write( + "No account -- using anonymous login.") + ftp.login(userid, passwd, acct) + for file in sys.argv[2:]: + if file[:2] == '-l': + ftp.dir(file[2:]) + elif file[:2] == '-d': + cmd = 'CWD' + if file[2:]: cmd = cmd + ' ' + file[2:] + resp = ftp.sendcmd(cmd) + elif file == '-p': + ftp.set_pasv(not ftp.passiveserver) + else: + ftp.retrbinary('RETR ' + file, \ + sys.stdout.write, 1024) + ftp.quit() + + +if __name__ == '__main__': + test() diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -700,6 +700,14 @@ except java.lang.Exception, jlx: raise _map_exception(jlx) +# +# Skeleton implementation of gethostbyname_ex +# Needed because urllib2 refers to it +# + +def gethostbyname_ex(name): + return (name, [], gethostbyname(name)) + def gethostbyaddr(name): names, addrs = _gethostbyaddr(name) return (names[0], names, addrs) @@ -1850,24 +1858,31 @@ class ssl: - def __init__(self, plain_sock, keyfile=None, certfile=None): + def __init__(self, jython_socket_wrapper, keyfile=None, certfile=None): try: - self.ssl_sock = self._make_ssl_socket(plain_sock) - self._in_buf = java.io.BufferedInputStream(self.ssl_sock.getInputStream()) - self._out_buf = java.io.BufferedOutputStream(self.ssl_sock.getOutputStream()) + self.jython_socket_wrapper = jython_socket_wrapper + jython_socket = self.jython_socket_wrapper._sock + self.java_ssl_socket = self._make_ssl_socket(jython_socket) + self._in_buf = java.io.BufferedInputStream(self.java_ssl_socket.getInputStream()) + self._out_buf = java.io.BufferedOutputStream(self.java_ssl_socket.getOutputStream()) except java.lang.Exception, jlx: raise _map_exception(jlx) - def _make_ssl_socket(self, plain_socket, auto_close=0): - java_net_socket = plain_socket._get_jsocket() + def _make_ssl_socket(self, jython_socket, auto_close=0): + java_net_socket = jython_socket._get_jsocket() assert isinstance(java_net_socket, java.net.Socket) host = java_net_socket.getInetAddress().getHostAddress() port = java_net_socket.getPort() factory = javax.net.ssl.SSLSocketFactory.getDefault(); - ssl_socket = factory.createSocket(java_net_socket, host, port, auto_close) - ssl_socket.setEnabledCipherSuites(ssl_socket.getSupportedCipherSuites()) - ssl_socket.startHandshake() - return ssl_socket + java_ssl_socket = factory.createSocket(java_net_socket, host, port, auto_close) + java_ssl_socket.setEnabledCipherSuites(java_ssl_socket.getSupportedCipherSuites()) + java_ssl_socket.startHandshake() + return java_ssl_socket + + def __getattr__(self, attr_name): + if hasattr(self.jython_socket_wrapper, attr_name): + return getattr(self.jython_socket_wrapper, attr_name) + raise AttributeError(attr_name) def read(self, n=4096): try: @@ -1891,7 +1906,7 @@ def _get_server_cert(self): try: - return self.ssl_sock.getSession().getPeerCertificates()[0] + return self.java_ssl_socket.getSession().getPeerCertificates()[0] except java.lang.Exception, jlx: raise _map_exception(jlx) @@ -1903,12 +1918,6 @@ cert = self._get_server_cert() return cert.getIssuerDN().toString() -_realssl = ssl -def ssl(sock, keyfile=None, certfile=None): - if hasattr(sock, "_sock"): - sock = sock._sock - return _realssl(sock, keyfile, certfile) - def test(): s = socket(AF_INET, SOCK_STREAM) s.connect(("", 80)) diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -7,4 +7,4 @@ import socket -wrap = socket.ssl +wrap_socket = socket.ssl diff --git a/Lib/test/test_socket_ssl.py b/Lib/test/test_socket_ssl.py --- a/Lib/test/test_socket_ssl.py +++ b/Lib/test/test_socket_ssl.py @@ -61,11 +61,20 @@ time.sleep(1) connector() +def test_https_socket(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(('www.verisign.com', 443)) + ssl_sock = socket.ssl(s) + ssl_sock.server() + ssl_sock.issuer() + s.close() + def test_main(): if not hasattr(socket, "ssl"): raise test_support.TestSkipped("socket module has no ssl support") test_rude_shutdown() test_basic() + test_https_socket() if __name__ == "__main__": test_main() diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -3,12 +3,16 @@ import urllib import httplib import unittest -from test import test_support import os +import sys import mimetools import tempfile import StringIO +from test import test_support +from base64 import b64encode + + def hexescape(char): """Escape char as RFC 2396 specifies""" hex_repr = hex(ord(char))[2:].upper() @@ -16,6 +20,43 @@ hex_repr = "0%s" % hex_repr return "%" + hex_repr + +class FakeHTTPMixin(object): + def fakehttp(self, fakedata): + class FakeSocket(StringIO.StringIO): + + def sendall(self, data): + FakeHTTPConnection.buf = data + + def makefile(self, *args, **kwds): + return self + + def read(self, amt=None): + if self.closed: + return "" + return StringIO.StringIO.read(self, amt) + + def readline(self, length=None): + if self.closed: + return "" + return StringIO.StringIO.readline(self, length) + + class FakeHTTPConnection(httplib.HTTPConnection): + + # buffer to store data for verification in urlopen tests. + buf = "" + + def connect(self): + self.sock = FakeSocket(fakedata) + + assert httplib.HTTP._connection_class == httplib.HTTPConnection + + httplib.HTTP._connection_class = FakeHTTPConnection + + def unfakehttp(self): + httplib.HTTP._connection_class = httplib.HTTPConnection + + class urlopen_FileTests(unittest.TestCase): """Test urlopen() opening a temporary file. @@ -44,7 +85,7 @@ # Make sure object returned by urlopen() has the specified methods for attr in ("read", "readline", "readlines", "fileno", "close", "info", "geturl", "getcode", "__iter__"): - self.assert_(hasattr(self.returned_obj, attr), + self.assertTrue(hasattr(self.returned_obj, attr), "object returned by urlopen() lacks %s attribute" % attr) @@ -79,7 +120,7 @@ self.returned_obj.close() def test_info(self): - self.assert_(isinstance(self.returned_obj.info(), mimetools.Message)) + self.assertIsInstance(self.returned_obj.info(), mimetools.Message) def test_geturl(self): self.assertEqual(self.returned_obj.geturl(), self.pathname) @@ -95,6 +136,9 @@ for line in self.returned_obj.__iter__(): self.assertEqual(line, self.text) + def test_relativelocalfile(self): + self.assertRaises(ValueError,urllib.urlopen,'./' + self.pathname) + class ProxyTests(unittest.TestCase): def setUp(self): @@ -114,31 +158,15 @@ self.env.set('NO_PROXY', 'localhost') proxies = urllib.getproxies_environment() # getproxies_environment use lowered case truncated (no '_proxy') keys - self.assertEquals('localhost', proxies['no']) + self.assertEqual('localhost', proxies['no']) + # List of no_proxies with space. + self.env.set('NO_PROXY', 'localhost, anotherdomain.com, newdomain.com') + self.assertTrue(urllib.proxy_bypass_environment('anotherdomain.com')) -class urlopen_HttpTests(unittest.TestCase): +class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin): """Test urlopen() opening a fake http connection.""" - def fakehttp(self, fakedata): - class FakeSocket(StringIO.StringIO): - def sendall(self, str): pass - def makefile(self, mode, name): return self - def read(self, amt=None): - if self.closed: return '' - return StringIO.StringIO.read(self, amt) - def readline(self, length=None): - if self.closed: return '' - return StringIO.StringIO.readline(self, length) - class FakeHTTPConnection(httplib.HTTPConnection): - def connect(self): - self.sock = FakeSocket(fakedata) - assert httplib.HTTP._connection_class == httplib.HTTPConnection - httplib.HTTP._connection_class = FakeHTTPConnection - - def unfakehttp(self): - httplib.HTTP._connection_class = httplib.HTTPConnection - def test_read(self): self.fakehttp('Hello!') try: @@ -150,6 +178,16 @@ finally: self.unfakehttp() + def test_url_fragment(self): + # Issue #11703: geturl() omits fragments in the original URL. + url = 'http://docs.python.org/library/urllib.html#OK' + self.fakehttp('Hello!') + try: + fp = urllib.urlopen(url) + self.assertEqual(fp.geturl(), url) + finally: + self.unfakehttp() + def test_read_bogus(self): # urlopen() should raise IOError for many error codes. self.fakehttp('''HTTP/1.1 401 Authentication Required @@ -186,6 +224,62 @@ finally: self.unfakehttp() + def test_missing_localfile(self): + self.assertRaises(IOError, urllib.urlopen, + 'file://localhost/a/missing/file.py') + fd, tmp_file = tempfile.mkstemp() + tmp_fileurl = 'file://localhost/' + tmp_file.replace(os.path.sep, '/') + try: + self.assertTrue(os.path.exists(tmp_file)) + fp = urllib.urlopen(tmp_fileurl) + finally: + os.close(fd) + fp.close() + os.unlink(tmp_file) + + self.assertFalse(os.path.exists(tmp_file)) + self.assertRaises(IOError, urllib.urlopen, tmp_fileurl) + + def test_ftp_nonexisting(self): + self.assertRaises(IOError, urllib.urlopen, + 'ftp://localhost/not/existing/file.py') + + + def test_userpass_inurl(self): + self.fakehttp('Hello!') + try: + fakehttp_wrapper = httplib.HTTP._connection_class + fp = urllib.urlopen("http://user:pass at python.org/") + authorization = ("Authorization: Basic %s\r\n" % + b64encode('user:pass')) + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf) + self.assertEqual(fp.readline(), "Hello!") + self.assertEqual(fp.readline(), "") + self.assertEqual(fp.geturl(), 'http://user:pass at python.org/') + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + def test_userpass_with_spaces_inurl(self): + self.fakehttp('Hello!') + try: + url = "http://a b:c d at python.org/" + fakehttp_wrapper = httplib.HTTP._connection_class + authorization = ("Authorization: Basic %s\r\n" % + b64encode('a b:c d')) + fp = urllib.urlopen(url) + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf) + self.assertEqual(fp.readline(), "Hello!") + self.assertEqual(fp.readline(), "") + # the spaces are quoted in URL so no match + self.assertNotEqual(fp.geturl(), url) + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + class urlretrieve_FileTests(unittest.TestCase): """Test urllib.urlretrieve() on local files""" @@ -243,9 +337,9 @@ # a headers value is returned. result = urllib.urlretrieve("file:%s" % test_support.TESTFN) self.assertEqual(result[0], test_support.TESTFN) - self.assert_(isinstance(result[1], mimetools.Message), - "did not get a mimetools.Message instance as second " - "returned value") + self.assertIsInstance(result[1], mimetools.Message, + "did not get a mimetools.Message instance as " + "second returned value") def test_copy(self): # Test that setting the filename argument works. @@ -254,7 +348,7 @@ result = urllib.urlretrieve(self.constructLocalFileUrl( test_support.TESTFN), second_temp) self.assertEqual(second_temp, result[0]) - self.assert_(os.path.exists(second_temp), "copy of the file was not " + self.assertTrue(os.path.exists(second_temp), "copy of the file was not " "made") FILE = file(second_temp, 'rb') try: @@ -268,9 +362,9 @@ def test_reporthook(self): # Make sure that the reporthook works. def hooktester(count, block_size, total_size, count_holder=[0]): - self.assert_(isinstance(count, int)) - self.assert_(isinstance(block_size, int)) - self.assert_(isinstance(total_size, int)) + self.assertIsInstance(count, int) + self.assertIsInstance(block_size, int) + self.assertIsInstance(total_size, int) self.assertEqual(count, count_holder[0]) count_holder[0] = count_holder[0] + 1 second_temp = "%s.2" % test_support.TESTFN @@ -318,6 +412,45 @@ self.assertEqual(report[0][1], 8192) self.assertEqual(report[0][2], 8193) + +class urlretrieve_HttpTests(unittest.TestCase, FakeHTTPMixin): + """Test urllib.urlretrieve() using fake http connections""" + + def test_short_content_raises_ContentTooShortError(self): + self.fakehttp('''HTTP/1.1 200 OK +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Connection: close +Content-Length: 100 +Content-Type: text/html; charset=iso-8859-1 + +FF +''') + + def _reporthook(par1, par2, par3): + pass + + try: + self.assertRaises(urllib.ContentTooShortError, urllib.urlretrieve, + 'http://example.com', reporthook=_reporthook) + finally: + self.unfakehttp() + + def test_short_content_raises_ContentTooShortError_without_reporthook(self): + self.fakehttp('''HTTP/1.1 200 OK +Date: Wed, 02 Jan 2008 03:03:54 GMT +Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e +Connection: close +Content-Length: 100 +Content-Type: text/html; charset=iso-8859-1 + +FF +''') + try: + self.assertRaises(urllib.ContentTooShortError, urllib.urlretrieve, 'http://example.com/') + finally: + self.unfakehttp() + class QuotingTests(unittest.TestCase): """Tests for urllib.quote() and urllib.quote_plus() @@ -395,8 +528,10 @@ result = urllib.quote(partial_quote) self.assertEqual(expected, result, "using quote(): %s != %s" % (expected, result)) + result = urllib.quote_plus(partial_quote) self.assertEqual(expected, result, "using quote_plus(): %s != %s" % (expected, result)) + self.assertRaises(TypeError, urllib.quote, None) def test_quoting_space(self): # Make sure quote() and quote_plus() handle spaces as specified in @@ -527,7 +662,7 @@ expect_somewhere = ["1st=1", "2nd=2", "3rd=3"] result = urllib.urlencode(given) for expected in expect_somewhere: - self.assert_(expected in result, + self.assertIn(expected, result, "testing %s: %s not found in %s" % (test_type, expected, result)) self.assertEqual(result.count('&'), 2, @@ -536,7 +671,7 @@ amp_location = result.index('&') on_amp_left = result[amp_location - 1] on_amp_right = result[amp_location + 1] - self.assert_(on_amp_left.isdigit() and on_amp_right.isdigit(), + self.assertTrue(on_amp_left.isdigit() and on_amp_right.isdigit(), "testing %s: '&' not located in proper place in %s" % (test_type, result)) self.assertEqual(len(result), (5 * 3) + 2, #5 chars per thing and amps @@ -574,8 +709,7 @@ result = urllib.urlencode(given, True) for value in given["sequence"]: expect = "sequence=%s" % value - self.assert_(expect in result, - "%s not found in %s" % (expect, result)) + self.assertIn(expect, result) self.assertEqual(result.count('&'), 2, "Expected 2 '&'s, got %s" % result.count('&')) @@ -622,8 +756,45 @@ "url2pathname() failed; %s != %s" % (expect, result)) + @unittest.skipUnless(sys.platform == 'win32', + 'test specific to the nturl2path library') + def test_ntpath(self): + given = ('/C:/', '///C:/', '/C|//') + expect = 'C:\\' + for url in given: + result = urllib.url2pathname(url) + self.assertEqual(expect, result, + 'nturl2path.url2pathname() failed; %s != %s' % + (expect, result)) + given = '///C|/path' + expect = 'C:\\path' + result = urllib.url2pathname(given) + self.assertEqual(expect, result, + 'nturl2path.url2pathname() failed; %s != %s' % + (expect, result)) + +class Utility_Tests(unittest.TestCase): + """Testcase to test the various utility functions in the urllib.""" + + def test_splitpasswd(self): + """Some of the password examples are not sensible, but it is added to + confirming to RFC2617 and addressing issue4675. + """ + self.assertEqual(('user', 'ab'),urllib.splitpasswd('user:ab')) + self.assertEqual(('user', 'a\nb'),urllib.splitpasswd('user:a\nb')) + self.assertEqual(('user', 'a\tb'),urllib.splitpasswd('user:a\tb')) + self.assertEqual(('user', 'a\rb'),urllib.splitpasswd('user:a\rb')) + self.assertEqual(('user', 'a\fb'),urllib.splitpasswd('user:a\fb')) + self.assertEqual(('user', 'a\vb'),urllib.splitpasswd('user:a\vb')) + self.assertEqual(('user', 'a:b'),urllib.splitpasswd('user:a:b')) + self.assertEqual(('user', 'a b'),urllib.splitpasswd('user:a b')) + self.assertEqual(('user 2', 'ab'),urllib.splitpasswd('user 2:ab')) + self.assertEqual(('user+1', 'a+b'),urllib.splitpasswd('user+1:a+b')) + + class URLopener_Tests(unittest.TestCase): """Testcase to test the open method of URLopener class.""" + def test_quoted_open(self): class DummyURLopener(urllib.URLopener): def open_spam(self, url): @@ -640,7 +811,7 @@ # Just commented them out. # Can't really tell why keep failing in windows and sparc. -# Everywhere else they work ok, but on those machines, someteimes +# Everywhere else they work ok, but on those machines, sometimes # fail in one of the tests, sometimes in other. I have a linux, and # the tests go ok. # If anybody has one of the problematic enviroments, please help! @@ -689,7 +860,7 @@ # def testTimeoutNone(self): # # global default timeout is ignored # import socket -# self.assert_(socket.getdefaulttimeout() is None) +# self.assertTrue(socket.getdefaulttimeout() is None) # socket.setdefaulttimeout(30) # try: # ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) @@ -701,7 +872,7 @@ # def testTimeoutDefault(self): # # global default timeout is used # import socket -# self.assert_(socket.getdefaulttimeout() is None) +# self.assertTrue(socket.getdefaulttimeout() is None) # socket.setdefaulttimeout(30) # try: # ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) @@ -727,11 +898,13 @@ urlopen_FileTests, urlopen_HttpTests, urlretrieve_FileTests, + urlretrieve_HttpTests, ProxyTests, QuotingTests, UnquotingTests, urlencode_Tests, Pathname_Tests, + Utility_Tests, URLopener_Tests, #FTPWrapperTests, ) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -293,6 +293,7 @@ self._tunnel_headers = headers else: self._tunnel_headers.clear() + def request(self, method, url, body=None, headers=None): self.method = method self.selector = url @@ -304,9 +305,13 @@ if self.raise_on_endheaders: import socket raise socket.error() + def getresponse(self): return MockHTTPResponse(MockFile(), {}, 200, "OK") + def close(self): + pass + class MockHandler: # useful for testing handler machinery # see add_ordered_mock_handlers() docstring @@ -593,21 +598,20 @@ def sanepathname2url(path): import urllib urlpath = urllib.pathname2url(path) - if ((os._name if test_support.is_jython else os.name) == 'nt' - and urlpath.startswith("///")): + if os.name == "nt" and urlpath.startswith("///"): urlpath = urlpath[2:] # XXX don't ask me about the mac... return urlpath class HandlerTests(unittest.TestCase): - @unittest.skip("FIXME: broken") def test_ftp(self): class MockFTPWrapper: def __init__(self, data): self.data = data def retrfile(self, filename, filetype): self.filename, self.filetype = filename, filetype return StringIO.StringIO(self.data), len(self.data) + def close(self): pass class NullFTPHandler(urllib2.FTPHandler): def __init__(self, data): self.data = data @@ -659,7 +663,6 @@ self.assertEqual(headers.get("Content-type"), mimetype) self.assertEqual(int(headers["Content-length"]), len(data)) - @unittest.skip("FIXME: not working") def test_file(self): import rfc822, socket h = urllib2.FileHandler() @@ -753,7 +756,6 @@ self.assertEqual(req.type, "ftp") self.assertEqual(req.type == "ftp", ftp) - @unittest.skip("FIXME: broken") def test_http(self): h = urllib2.AbstractHTTPHandler() @@ -842,7 +844,6 @@ p_ds_req = h.do_request_(ds_req) self.assertEqual(p_ds_req.unredirected_hdrs["Host"],"example.com") - @unittest.skip("FIXME: broken") def test_fixpath_in_weirdurls(self): # Issue4493: urllib2 to supply '/' when to urls where path does not # start with'/' @@ -974,6 +975,28 @@ self.assertEqual(count, urllib2.HTTPRedirectHandler.max_redirections) + def test_invalid_redirect(self): + from_url = "http://example.com/a.html" + valid_schemes = ['http', 'https', 'ftp'] + invalid_schemes = ['file', 'imap', 'ldap'] + schemeless_url = "example.com/b.html" + h = urllib2.HTTPRedirectHandler() + o = h.parent = MockOpener() + req = Request(from_url) + req.timeout = socket._GLOBAL_DEFAULT_TIMEOUT + + for scheme in invalid_schemes: + invalid_url = scheme + '://' + schemeless_url + self.assertRaises(urllib2.HTTPError, h.http_error_302, + req, MockFile(), 302, "Security Loophole", + MockHeaders({"location": invalid_url})) + + for scheme in valid_schemes: + valid_url = scheme + '://' + schemeless_url + h.http_error_302(req, MockFile(), 302, "That's fine", + MockHeaders({"location": valid_url})) + self.assertEqual(o.req.get_full_url(), valid_url) + def test_cookie_redirect(self): # cookies shouldn't leak into redirected requests from cookielib import CookieJar @@ -990,6 +1013,15 @@ o.open("http://www.example.com/") self.assertTrue(not hh.req.has_header("Cookie")) + def test_redirect_fragment(self): + redirected_url = 'http://www.example.com/index.html#OK\r\n\r\n' + hh = MockHTTPHandler(302, 'Location: ' + redirected_url) + hdeh = urllib2.HTTPDefaultErrorHandler() + hrh = urllib2.HTTPRedirectHandler() + o = build_test_opener(hh, hdeh, hrh) + fp = o.open('http://www.example.com') + self.assertEqual(fp.geturl(), redirected_url.strip()) + def test_proxy(self): o = OpenerDirector() ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128")) @@ -1074,12 +1106,31 @@ self._test_basic_auth(opener, auth_handler, "Authorization", realm, http_handler, password_manager, "http://acme.example.com/protected", - "http://acme.example.com/protected", - ) + "http://acme.example.com/protected" + ) def test_basic_auth_with_single_quoted_realm(self): self.test_basic_auth(quote_char="'") + @unittest.skipIf(test_support.is_jython, "Currently not working on jython") + def test_basic_auth_with_unquoted_realm(self): + opener = OpenerDirector() + password_manager = MockPasswordManager() + auth_handler = urllib2.HTTPBasicAuthHandler(password_manager) + realm = "ACME Widget Store" + http_handler = MockHTTPHandler( + 401, 'WWW-Authenticate: Basic realm=%s\r\n\r\n' % realm) + opener.add_handler(auth_handler) + opener.add_handler(http_handler) + msg = "Basic Auth Realm was unquoted" + with test_support.check_warnings((msg, UserWarning)): + self._test_basic_auth(opener, auth_handler, "Authorization", + realm, http_handler, password_manager, + "http://acme.example.com/protected", + "http://acme.example.com/protected" + ) + + def test_proxy_basic_auth(self): opener = OpenerDirector() ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128")) @@ -1098,7 +1149,7 @@ ) def test_basic_and_digest_auth_handlers(self): - # HTTPDigestAuthHandler threw an exception if it couldn't handle a 40* + # HTTPDigestAuthHandler raised an exception if it couldn't handle a 40* # response (http://python.org/sf/1479302), where it should instead # return None to allow another handler (especially # HTTPBasicAuthHandler) to handle the response. @@ -1275,12 +1326,44 @@ req = Request("") self.assertEqual("www.python.org", req.get_host()) - def test_urlwith_fragment(self): + def test_url_fragment(self): req = Request("http://www.python.org/?qs=query#fragment=true") self.assertEqual("/?qs=query", req.get_selector()) req = Request("http://www.python.org/#fun=true") self.assertEqual("/", req.get_selector()) + # Issue 11703: geturl() omits fragment in the original URL. + url = 'http://docs.python.org/library/urllib2.html#OK' + req = Request(url) + self.assertEqual(req.get_full_url(), url) + + def test_HTTPError_interface(self): + """ + Issue 13211 reveals that HTTPError didn't implement the URLError + interface even though HTTPError is a subclass of URLError. + + >>> err = urllib2.HTTPError(msg='something bad happened', url=None, code=None, hdrs=None, fp=None) + >>> assert hasattr(err, 'reason') + >>> err.reason + 'something bad happened' + """ + + @unittest.skip("Test is broken because of fp=None, which causes failure to call addinfourl superclass __init__") + def test_HTTPError_interface_call(self): + """ + Issue 15701= - HTTPError interface has info method available from URLError. + """ + err = urllib2.HTTPError(msg='something bad happened', url=None, + code=None, hdrs='Content-Length:42', fp=None) + self.assertTrue(hasattr(err, 'reason')) + assert hasattr(err, 'reason') + assert hasattr(err, 'info') + assert callable(err.info) + try: + err.info() + except AttributeError: + self.fail("err.info() failed") + self.assertEqual(err.info(), "Content-Length:42") def test_main(verbose=None): from test import test_urllib2 diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -1,14 +1,21 @@ #!/usr/bin/env python -import sys -import threading import urlparse import urllib2 import BaseHTTPServer import unittest import hashlib + from test import test_support +if test_support.is_jython: + import socket + # Working around an IPV6 problem on Windows + socket._use_ipv4_addresses_only(True) + +mimetools = test_support.import_module('mimetools', deprecated=True) +threading = test_support.import_module('threading') + # Loopback http server infrastructure class LoopbackHttpServer(BaseHTTPServer.HTTPServer): @@ -19,7 +26,12 @@ def __init__(self, server_address, RequestHandlerClass): BaseHTTPServer.HTTPServer.__init__(self, server_address, - RequestHandlerClass) + RequestHandlerClass, + True) + + host, port = self.socket.getsockname()[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port # Set the timeout of our listening socket really low so # that we can stop the server easily. @@ -40,13 +52,16 @@ class LoopbackHttpServerThread(threading.Thread): """Stoppable thread that runs a loopback http server.""" - def __init__(self, port, RequestHandlerClass): + def __init__(self, request_handler): threading.Thread.__init__(self) - self._RequestHandlerClass = RequestHandlerClass self._stop = False - self._port = port - self._server_address = ('127.0.0.1', self._port) self.ready = threading.Event() + request_handler.protocol_version = "HTTP/1.0" + self.httpd = LoopbackHttpServer(('127.0.0.1', 0), + request_handler) + #print "Serving HTTP on %s port %s" % (self.httpd.server_name, + # self.httpd.server_port) + self.port = self.httpd.server_port def stop(self): """Stops the webserver if it's currently running.""" @@ -57,19 +72,9 @@ self.join() def run(self): - protocol = "HTTP/1.0" - - self._RequestHandlerClass.protocol_version = protocol - httpd = LoopbackHttpServer(self._server_address, - self._RequestHandlerClass) - - sa = httpd.socket.getsockname() - #print "Serving HTTP on", sa[0], "port", sa[1], "..." - self.ready.set() while not self._stop: - httpd.handle_request() - httpd.server_close() + self.httpd.handle_request() # Authentication infrastructure @@ -161,13 +166,13 @@ if len(self._users) == 0: return True - if not request_handler.headers.has_key('Proxy-Authorization'): + if 'Proxy-Authorization' not in request_handler.headers: return self._return_auth_challenge(request_handler) else: auth_dict = self._create_auth_dict( request_handler.headers['Proxy-Authorization'] ) - if self._users.has_key(auth_dict["username"]): + if auth_dict["username"] in self._users: password = self._users[ auth_dict["username"] ] else: return self._return_auth_challenge(request_handler) @@ -202,7 +207,11 @@ testing. """ - digest_auth_handler = DigestAuthHandler() + def __init__(self, digest_auth_handler, *args, **kwargs): + # This has to be set before calling our parent's __init__(), which will + # try to call do_GET(). + self.digest_auth_handler = digest_auth_handler + BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs) def log_message(self, format, *args): # Uncomment the next line for debugging. @@ -223,60 +232,68 @@ # Test cases -class ProxyAuthTests(unittest.TestCase): - URL = "http://www.foo.com" +class BaseTestCase(unittest.TestCase): + def setUp(self): + self._threads = test_support.threading_setup() - PORT = 58080 + def tearDown(self): + test_support.threading_cleanup(*self._threads) + + +class ProxyAuthTests(BaseTestCase): + URL = "http://localhost" + USER = "tester" PASSWD = "test123" REALM = "TestRealm" - PROXY_URL = "http://127.0.0.1:%d" % PORT + def setUp(self): + super(ProxyAuthTests, self).setUp() + self.digest_auth_handler = DigestAuthHandler() + self.digest_auth_handler.set_users({self.USER: self.PASSWD}) + self.digest_auth_handler.set_realm(self.REALM) + def create_fake_proxy_handler(*args, **kwargs): + return FakeProxyHandler(self.digest_auth_handler, *args, **kwargs) - def setUp(self): - FakeProxyHandler.digest_auth_handler.set_users({ - self.USER : self.PASSWD - }) - FakeProxyHandler.digest_auth_handler.set_realm(self.REALM) - - self.server = LoopbackHttpServerThread(self.PORT, FakeProxyHandler) + self.server = LoopbackHttpServerThread(create_fake_proxy_handler) self.server.start() self.server.ready.wait() - - handler = urllib2.ProxyHandler({"http" : self.PROXY_URL}) - self._digest_auth_handler = urllib2.ProxyDigestAuthHandler() - self.opener = urllib2.build_opener(handler, self._digest_auth_handler) + proxy_url = "http://127.0.0.1:%d" % self.server.port + handler = urllib2.ProxyHandler({"http" : proxy_url}) + self.proxy_digest_handler = urllib2.ProxyDigestAuthHandler() + self.opener = urllib2.build_opener(handler, self.proxy_digest_handler) def tearDown(self): self.server.stop() + super(ProxyAuthTests, self).tearDown() def test_proxy_with_bad_password_raises_httperror(self): - self._digest_auth_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD+"bad") - FakeProxyHandler.digest_auth_handler.set_qop("auth") + self.digest_auth_handler.set_qop("auth") self.assertRaises(urllib2.HTTPError, self.opener.open, self.URL) def test_proxy_with_no_password_raises_httperror(self): - FakeProxyHandler.digest_auth_handler.set_qop("auth") + self.digest_auth_handler.set_qop("auth") self.assertRaises(urllib2.HTTPError, self.opener.open, self.URL) def test_proxy_qop_auth_works(self): - self._digest_auth_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD) - FakeProxyHandler.digest_auth_handler.set_qop("auth") + self.digest_auth_handler.set_qop("auth") result = self.opener.open(self.URL) while result.read(): pass result.close() def test_proxy_qop_auth_int_works_or_throws_urlerror(self): - self._digest_auth_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD) - FakeProxyHandler.digest_auth_handler.set_qop("auth-int") + self.digest_auth_handler.set_qop("auth-int") try: result = self.opener.open(self.URL) except urllib2.URLError: @@ -289,6 +306,244 @@ pass result.close() + +def GetRequestHandler(responses): + + class FakeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + + server_version = "TestHTTP/" + requests = [] + headers_received = [] + port = 80 + + def do_GET(self): + body = self.send_head() + if body: + self.wfile.write(body) + + def do_POST(self): + content_length = self.headers['Content-Length'] + post_data = self.rfile.read(int(content_length)) + self.do_GET() + self.requests.append(post_data) + + def send_head(self): + FakeHTTPRequestHandler.headers_received = self.headers + self.requests.append(self.path) + response_code, headers, body = responses.pop(0) + + self.send_response(response_code) + + for (header, value) in headers: + self.send_header(header, value % self.port) + if body: + self.send_header('Content-type', 'text/plain') + self.end_headers() + return body + self.end_headers() + + def log_message(self, *args): + pass + + + return FakeHTTPRequestHandler + + +class TestUrlopen(BaseTestCase): + """Tests urllib2.urlopen using the network. + + These tests are not exhaustive. Assuming that testing using files does a + good job overall of some of the basic interface features. There are no + tests exercising the optional 'data' and 'proxies' arguments. No tests + for transparent redirection have been written. + """ + + def setUp(self): + proxy_handler = urllib2.ProxyHandler({}) + opener = urllib2.build_opener(proxy_handler) + urllib2.install_opener(opener) + super(TestUrlopen, self).setUp() + + def start_server(self, responses): + handler = GetRequestHandler(responses) + + self.server = LoopbackHttpServerThread(handler) + self.server.start() + self.server.ready.wait() + port = self.server.port + handler.port = port + return handler + + + def test_redirection(self): + expected_response = 'We got here...' + responses = [ + (302, [('Location', 'http://localhost:%s/somewhere_else')], ''), + (200, [], expected_response) + ] + + handler = self.start_server(responses) + + try: + f = urllib2.urlopen('http://localhost:%s/' % handler.port) + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/', '/somewhere_else']) + finally: + self.server.stop() + + + def test_404(self): + expected_response = 'Bad bad bad...' + handler = self.start_server([(404, [], expected_response)]) + + try: + try: + urllib2.urlopen('http://localhost:%s/weeble' % handler.port) + except urllib2.URLError, f: + pass + else: + self.fail('404 should raise URLError') + + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/weeble']) + finally: + self.server.stop() + + + def test_200(self): + expected_response = 'pycon 2008...' + handler = self.start_server([(200, [], expected_response)]) + + try: + f = urllib2.urlopen('http://localhost:%s/bizarre' % handler.port) + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/bizarre']) + finally: + self.server.stop() + + def test_200_with_parameters(self): + expected_response = 'pycon 2008...' + handler = self.start_server([(200, [], expected_response)]) + + try: + f = urllib2.urlopen('http://localhost:%s/bizarre' % handler.port, 'get=with_feeling') + data = f.read() + f.close() + + self.assertEqual(data, expected_response) + self.assertEqual(handler.requests, ['/bizarre', 'get=with_feeling']) + finally: + self.server.stop() + + + def test_sending_headers(self): + handler = self.start_server([(200, [], "we don't care")]) + + try: + req = urllib2.Request("http://localhost:%s/" % handler.port, + headers={'Range': 'bytes=20-39'}) + urllib2.urlopen(req) + self.assertEqual(handler.headers_received['Range'], 'bytes=20-39') + finally: + self.server.stop() + + def test_basic(self): + handler = self.start_server([(200, [], "we don't care")]) + + try: + open_url = urllib2.urlopen("http://localhost:%s" % handler.port) + for attr in ("read", "close", "info", "geturl"): + self.assertTrue(hasattr(open_url, attr), "object returned from " + "urlopen lacks the %s attribute" % attr) + try: + self.assertTrue(open_url.read(), "calling 'read' failed") + finally: + open_url.close() + finally: + self.server.stop() + + def test_info(self): + handler = self.start_server([(200, [], "we don't care")]) + + try: + open_url = urllib2.urlopen("http://localhost:%s" % handler.port) + info_obj = open_url.info() + self.assertIsInstance(info_obj, mimetools.Message, + "object returned by 'info' is not an " + "instance of mimetools.Message") + self.assertEqual(info_obj.getsubtype(), "plain") + finally: + self.server.stop() + + def test_geturl(self): + # Make sure same URL as opened is returned by geturl. + handler = self.start_server([(200, [], "we don't care")]) + + try: + open_url = urllib2.urlopen("http://localhost:%s" % handler.port) + url = open_url.geturl() + self.assertEqual(url, "http://localhost:%s" % handler.port) + finally: + self.server.stop() + + + def test_bad_address(self): + # Make sure proper exception is raised when connecting to a bogus + # address. + self.assertRaises(IOError, + # Given that both VeriSign and various ISPs have in + # the past or are presently hijacking various invalid + # domain name requests in an attempt to boost traffic + # to their own sites, finding a domain name to use + # for this test is difficult. RFC2606 leads one to + # believe that '.invalid' should work, but experience + # seemed to indicate otherwise. Single character + # TLDs are likely to remain invalid, so this seems to + # be the best choice. The trailing '.' prevents a + # related problem: The normal DNS resolver appends + # the domain names from the search path if there is + # no '.' the end and, and if one of those domains + # implements a '*' rule a result is returned. + # However, none of this will prevent the test from + # failing if the ISP hijacks all invalid domain + # requests. The real solution would be to be able to + # parameterize the framework with a mock resolver. + urllib2.urlopen, "http://sadflkjsasf.i.nvali.d./") + + def test_iteration(self): + expected_response = "pycon 2008..." + handler = self.start_server([(200, [], expected_response)]) + try: + data = urllib2.urlopen("http://localhost:%s" % handler.port) + for line in data: + self.assertEqual(line, expected_response) + finally: + self.server.stop() + + def ztest_line_iteration(self): + lines = ["We\n", "got\n", "here\n", "verylong " * 8192 + "\n"] + expected_response = "".join(lines) + handler = self.start_server([(200, [], expected_response)]) + try: + data = urllib2.urlopen("http://localhost:%s" % handler.port) + for index, line in enumerate(data): + self.assertEqual(line, lines[index], + "Fetched line number %s doesn't match expected:\n" + " Expected length was %s, got %s" % + (index, len(lines[index]), len(line))) + finally: + self.server.stop() + self.assertEqual(index + 1, len(lines)) + def test_main(): # We will NOT depend on the network resource flag # (Lib/test/regrtest.py -u network) since all tests here are only @@ -296,7 +551,7 @@ # the next line. #test_support.requires("network") - test_support.run_unittest(ProxyAuthTests) + test_support.run_unittest(ProxyAuthTests, TestUrlopen) if __name__ == "__main__": test_main() diff --git a/Lib/urllib.py b/Lib/urllib.py --- a/Lib/urllib.py +++ b/Lib/urllib.py @@ -27,6 +27,8 @@ import os import time import sys +import base64 + from urlparse import urljoin as basejoin __all__ = ["urlopen", "URLopener", "FancyURLopener", "urlretrieve", @@ -42,9 +44,7 @@ MAXFTPCACHE = 10 # Trim the ftp cache beyond this size # Helper for non-unix systems -if os.name == 'mac': - from macurl2path import url2pathname, pathname2url -elif (os._name if sys.platform.startswith('java') else os.name) == 'nt': +if (os._name if sys.platform.startswith('java') else os.name) == 'nt': from nturl2path import url2pathname, pathname2url elif os.name == 'riscos': from rourl2path import url2pathname, pathname2url @@ -94,7 +94,7 @@ def urlcleanup(): if _urlopener: _urlopener.cleanup() - _safemaps.clear() + _safe_quoters.clear() ftpcache.clear() # check for SSL @@ -177,8 +177,8 @@ def open(self, fullurl, data=None): """Use URLopener().open(file) instead of open(file, 'r').""" fullurl = unwrap(toBytes(fullurl)) - # percent encode url. fixing lame server errors like space within url - # parts + # percent encode url, fixing lame server errors for e.g, like space + # within url paths. fullurl = quote(fullurl, safe="%/:=&?~#+!$,;'@()*[]|") if self.tempcache and fullurl in self.tempcache: filename, headers = self.tempcache[fullurl] @@ -232,9 +232,9 @@ try: fp = self.open_local_file(url1) hdrs = fp.info() - del fp + fp.close() return url2pathname(splithost(url1)[1]), hdrs - except IOError, msg: + except IOError: pass fp = self.open(url, data) try: @@ -259,9 +259,9 @@ size = -1 read = 0 blocknum = 0 + if "content-length" in headers: + size = int(headers["Content-Length"]) if reporthook: - if "content-length" in headers: - size = int(headers["Content-Length"]) reporthook(blocknum, bs, size) while 1: block = fp.read(bs) @@ -276,8 +276,6 @@ tfp.close() finally: fp.close() - del fp - del tfp # raise exception if actual size does not match content-length header if size >= 0 and read < size: @@ -322,13 +320,13 @@ if not host: raise IOError, ('http error', 'no host given') if proxy_passwd: - import base64 + proxy_passwd = unquote(proxy_passwd) proxy_auth = base64.b64encode(proxy_passwd).strip() else: proxy_auth = None if user_passwd: - import base64 + user_passwd = unquote(user_passwd) auth = base64.b64encode(user_passwd).strip() else: auth = None @@ -343,9 +341,7 @@ if auth: h.putheader('Authorization', 'Basic %s' % auth) if realhost: h.putheader('Host', realhost) for args in self.addheaders: h.putheader(*args) - h.endheaders() - if data is not None: - h.send(data) + h.endheaders(data) errcode, errmsg, headers = h.getreply() fp = h.getfile() if errcode == -1: @@ -380,7 +376,6 @@ def http_error_default(self, url, fp, errcode, errmsg, headers): """Default error handler: close the connection and raise IOError.""" - void = fp.read() fp.close() raise IOError, ('http error', errcode, errmsg, headers) @@ -415,12 +410,12 @@ #print "proxy via https:", host, selector if not host: raise IOError, ('https error', 'no host given') if proxy_passwd: - import base64 + proxy_passwd = unquote(proxy_passwd) proxy_auth = base64.b64encode(proxy_passwd).strip() else: proxy_auth = None if user_passwd: - import base64 + user_passwd = unquote(user_passwd) auth = base64.b64encode(user_passwd).strip() else: auth = None @@ -438,9 +433,7 @@ if auth: h.putheader('Authorization', 'Basic %s' % auth) if realhost: h.putheader('Host', realhost) for args in self.addheaders: h.putheader(*args) - h.endheaders() - if data is not None: - h.send(data) + h.endheaders(data) errcode, errmsg, headers = h.getreply() fp = h.getfile() if errcode == -1: @@ -491,6 +484,8 @@ urlfile = file if file[:1] == '/': urlfile = 'file://' + file + elif file[:2] == './': + raise ValueError("local file url may start with / or file:. Unknown url of type: %s" % url) return addinfourl(open(localname, 'rb'), headers, urlfile) host, port = splitport(host) @@ -519,8 +514,8 @@ if user: user, passwd = splitpasswd(user) else: passwd = None host = unquote(host) - user = unquote(user or '') - passwd = unquote(passwd or '') + user = user or '' + passwd = passwd or '' host = socket.gethostbyname(host) if not port: import ftplib @@ -598,7 +593,6 @@ time.gmtime(time.time()))) msg.append('Content-type: %s' % type) if encoding == 'base64': - import base64 data = base64.decodestring(data) else: data = unquote(data) @@ -648,7 +642,6 @@ newurl = headers['uri'] else: return - void = fp.read() fp.close() # In case the server sent a relative URL, join with original: newurl = basejoin(self.type + ":" + url, newurl) @@ -785,7 +778,7 @@ else: return self.open(newurl, data) - def get_user_passwd(self, host, realm, clear_cache = 0): + def get_user_passwd(self, host, realm, clear_cache=0): key = realm + '@' + host.lower() if key in self.auth_cache: if clear_cache: @@ -858,13 +851,16 @@ """Class used by open_ftp() for cache of open FTP connections.""" def __init__(self, user, passwd, host, port, dirs, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + persistent=True): self.user = user self.passwd = passwd self.host = host self.port = port self.dirs = dirs self.timeout = timeout + self.refcount = 0 + self.keepalive = persistent self.init() def init(self): @@ -891,7 +887,7 @@ # Try to retrieve as a file try: cmd = 'RETR ' + file - conn = self.ftp.ntransfercmd(cmd) + conn, retrlen = self.ftp.ntransfercmd(cmd) except ftplib.error_perm, reason: if str(reason)[:3] != '550': raise IOError, ('ftp error', reason), sys.exc_info()[2] @@ -911,11 +907,14 @@ cmd = 'LIST ' + file else: cmd = 'LIST' - conn = self.ftp.ntransfercmd(cmd) + conn, retrlen = self.ftp.ntransfercmd(cmd) self.busy = 1 + ftpobj = addclosehook(conn.makefile('rb'), self.file_close) + self.refcount += 1 + conn.close() # Pass back both a suitably decorated object and a retrieval length - return (addclosehook(conn[0].makefile('rb'), - self.endtransfer), conn[1]) + return (ftpobj, retrlen) + def endtransfer(self): if not self.busy: return @@ -926,6 +925,17 @@ pass def close(self): + self.keepalive = False + if self.refcount <= 0: + self.real_close() + + def file_close(self): + self.endtransfer() + self.refcount -= 1 + if self.refcount <= 0 and not self.keepalive: + self.real_close() + + def real_close(self): self.endtransfer() try: self.ftp.close() @@ -970,11 +980,11 @@ self.hookargs = hookargs def close(self): - addbase.close(self) if self.closehook: self.closehook(*self.hookargs) self.closehook = None self.hookargs = None + addbase.close(self) class addinfo(addbase): """class to add an info() method to an open file.""" @@ -1072,7 +1082,12 @@ _hostprog = re.compile('^//([^/?]*)(.*)$') match = _hostprog.match(url) - if match: return match.group(1, 2) + if match: + host_port = match.group(1) + path = match.group(2) + if path and not path.startswith('/'): + path = '/' + path + return host_port, path return None, url _userprog = None @@ -1084,7 +1099,7 @@ _userprog = re.compile('^(.*)@(.*)$') match = _userprog.match(host) - if match: return map(unquote, match.group(1, 2)) + if match: return match.group(1, 2) return None, host _passwdprog = None @@ -1093,7 +1108,7 @@ global _passwdprog if _passwdprog is None: import re - _passwdprog = re.compile('^([^:]*):(.*)$') + _passwdprog = re.compile('^([^:]*):(.*)$',re.S) match = _passwdprog.match(user) if match: return match.group(1, 2) @@ -1176,21 +1191,29 @@ if match: return match.group(1, 2) return attr, None +# urlparse contains a duplicate of this method to avoid a circular import. If +# you update this method, also update the copy in urlparse. This code +# duplication does not exist in Python3. + _hexdig = '0123456789ABCDEFabcdef' -_hextochr = dict((a+b, chr(int(a+b,16))) for a in _hexdig for b in _hexdig) +_hextochr = dict((a + b, chr(int(a + b, 16))) + for a in _hexdig for b in _hexdig) def unquote(s): """unquote('abc%20def') -> 'abc def'.""" res = s.split('%') - for i in xrange(1, len(res)): - item = res[i] + # fastpath + if len(res) == 1: + return s + s = res[0] + for item in res[1:]: try: - res[i] = _hextochr[item[:2]] + item[2:] + s += _hextochr[item[:2]] + item[2:] except KeyError: - res[i] = '%' + item + s += '%' + item except UnicodeDecodeError: - res[i] = unichr(int(item[:2], 16)) + item[2:] - return "".join(res) + s += unichr(int(item[:2], 16)) + item[2:] + return s def unquote_plus(s): """unquote('%7e/abc+def') -> '~/abc def'""" @@ -1200,9 +1223,12 @@ always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' '0123456789' '_.-') -_safemaps = {} +_safe_map = {} +for i, c in zip(xrange(256), str(bytearray(xrange(256)))): + _safe_map[c] = c if (i < 128 and c in always_safe) else '%{:02X}'.format(i) +_safe_quoters = {} -def quote(s, safe = '/'): +def quote(s, safe='/'): """quote('abc def') -> 'abc%20def' Each part of a URL, e.g. the path info, the query, etc., has a @@ -1223,27 +1249,32 @@ called on a path where the existing slash characters are used as reserved characters. """ + # fastpath + if not s: + if s is None: + raise TypeError('None object cannot be quoted') + return s cachekey = (safe, always_safe) try: - safe_map = _safemaps[cachekey] + (quoter, safe) = _safe_quoters[cachekey] except KeyError: - safe += always_safe - safe_map = {} - for i in range(256): - c = chr(i) - safe_map[c] = (c in safe) and c or ('%%%02X' % i) - _safemaps[cachekey] = safe_map - res = map(safe_map.__getitem__, s) - return ''.join(res) + safe_map = _safe_map.copy() + safe_map.update([(c, c) for c in safe]) + quoter = safe_map.__getitem__ + safe = always_safe + safe + _safe_quoters[cachekey] = (quoter, safe) + if not s.rstrip(safe): + return s + return ''.join(map(quoter, s)) -def quote_plus(s, safe = ''): +def quote_plus(s, safe=''): """Quote the query fragment of a URL; replacing ' ' with '+'""" if ' ' in s: s = quote(s, safe + ' ') return s.replace(' ', '+') return quote(s, safe) -def urlencode(query,doseq=0): +def urlencode(query, doseq=0): """Encode a sequence of two-element tuples or dictionary into a URL query string. If any values in the query arg are sequences and doseq is true, each @@ -1295,7 +1326,7 @@ else: try: # is this a sufficient test for sequence-ness? - x = len(v) + len(v) except TypeError: # not a sequence v = quote_plus(str(v)) @@ -1336,7 +1367,8 @@ # strip port off host hostonly, port = splitport(host) # check if the host ends with any of the DNS suffixes - for name in no_proxy.split(','): + no_proxy_list = [proxy.strip() for proxy in no_proxy.split(',')] + for name in no_proxy_list: if name and (hostonly.endswith(name) or host.endswith(name)): return 1 # otherwise, don't bypass @@ -1395,7 +1427,7 @@ else: mask = int(mask[1:]) - mask = 32 - mask + mask = 32 - mask if (hostIP >> mask) == (base >> mask): return True @@ -1405,7 +1437,6 @@ return False - def getproxies_macosx_sysconf(): """Return a dictionary of scheme -> proxy server URL mappings. @@ -1414,8 +1445,6 @@ """ return _get_proxies() - - def proxy_bypass(host): if getproxies_environment(): return proxy_bypass_environment(host) @@ -1519,18 +1548,11 @@ # '' string by the localhost entry and the corresponding # canonical entry. proxyOverride = proxyOverride.split(';') - i = 0 - while i < len(proxyOverride): - if proxyOverride[i] == '': - proxyOverride[i:i+1] = ['localhost', - '127.0.0.1', - socket.gethostname(), - socket.gethostbyname( - socket.gethostname())] - i += 1 - # print proxyOverride # now check if we match one of the registry values. for test in proxyOverride: + if test == '': + if '.' not in rawHost: + return 1 test = test.replace(".", r"\.") # mask dots test = test.replace("*", r".*") # change glob sequence test = test.replace("?", r".") # change glob char @@ -1578,67 +1600,3 @@ # Report during remote transfers print "Block number: %d, Block size: %d, Total size: %d" % ( blocknum, blocksize, totalsize) - -# Test program -def test(args=[]): - if not args: - args = [ - '/etc/passwd', - 'file:/etc/passwd', - 'file://localhost/etc/passwd', - 'ftp://ftp.gnu.org/pub/README', - 'http://www.python.org/index.html', - ] - if hasattr(URLopener, "open_https"): - args.append('https://synergy.as.cmu.edu/~geek/') - try: - for url in args: - print '-'*10, url, '-'*10 - fn, h = urlretrieve(url, None, reporthook) - print fn - if h: - print '======' - for k in h.keys(): print k + ':', h[k] - print '======' - fp = open(fn, 'rb') - data = fp.read() - del fp - if '\r' in data: - table = string.maketrans("", "") - data = data.translate(table, "\r") - print data - fn, h = None, None - print '-'*40 - finally: - urlcleanup() - -def main(): - import getopt, sys - try: - opts, args = getopt.getopt(sys.argv[1:], "th") - except getopt.error, msg: - print msg - print "Use -h for help" - return - t = 0 - for o, a in opts: - if o == '-t': - t = t + 1 - if o == '-h': - print "Usage: python urllib.py [-t] [url ...]" - print "-t runs self-test;", - print "otherwise, contents of urls are printed" - return - if t: - if t > 1: - test1() - test(args) - else: - if not args: - print "Use -h for help" - for url in args: - print urlopen(url).read(), - -# Run test program when run as a script -if __name__ == '__main__': - main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 19:53:38 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 19:53:38 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231327_ThreadState_API_cle?= =?utf-8?q?anup_work=2E_Thanks_Jim_Baker_and_Matt_Brinkley!?= Message-ID: <3Z0J3Q2tqPzR16@mail.python.org> http://hg.python.org/jython/rev/39f52bfcad4f changeset: 6985:39f52bfcad4f user: Frank Wierzbicki date: Mon Feb 04 10:53:26 2013 -0800 summary: #1327 ThreadState API cleanup work. Thanks Jim Baker and Matt Brinkley! files: NEWS | 1 + src/org/python/core/Py.java | 4 +- src/org/python/core/PyObject.java | 6 +- src/org/python/core/PyReflectedConstructor.java | 6 +- src/org/python/core/ThreadState.java | 23 ---------- 5 files changed, 9 insertions(+), 31 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7a3 Bugs Fixed + - [ 1327 ] ThreadState needs API cleanup work - [ 1309 ] Server sockets do not support client options and propagate them to 'accept'ed client sockets. - [ 1951 ] Bytecode Interpreter stack optimization for larger arguments - [ 1894 ] bytearray does not support '+' or .join() diff --git a/src/org/python/core/Py.java b/src/org/python/core/Py.java --- a/src/org/python/core/Py.java +++ b/src/org/python/core/Py.java @@ -934,8 +934,8 @@ { if (proxy._getPyInstance() != null) return; - ThreadState ts = getThreadState(); - PyObject instance = ts.getInitializingProxy(); + PyObject instance = ThreadContext.initializingProxy.get(); + ThreadState ts = Py.getThreadState(); if (instance != null) { if (instance.javaProxy != null) { throw Py.TypeError("Proxy instance reused"); diff --git a/src/org/python/core/PyObject.java b/src/org/python/core/PyObject.java --- a/src/org/python/core/PyObject.java +++ b/src/org/python/core/PyObject.java @@ -146,9 +146,9 @@ throw Py.SystemError("Automatic proxy initialization should only occur on proxy classes"); } PyProxy proxy; - ThreadState ts = Py.getThreadState(); + PyObject previous = ThreadContext.initializingProxy.get(); + ThreadContext.initializingProxy.set(this); try { - ts.pushInitializingProxy(this); try { proxy = (PyProxy)c.newInstance(); } catch (java.lang.InstantiationException e) { @@ -164,7 +164,7 @@ throw Py.JavaError(exc); } } finally { - ts.popInitializingProxy(); + ThreadContext.initializingProxy.set(previous); } if (javaProxy != null && javaProxy != proxy) { throw Py.TypeError("Proxy instance already initialized"); diff --git a/src/org/python/core/PyReflectedConstructor.java b/src/org/python/core/PyReflectedConstructor.java --- a/src/org/python/core/PyReflectedConstructor.java +++ b/src/org/python/core/PyReflectedConstructor.java @@ -203,9 +203,9 @@ protected void constructProxy(PyObject obj, Constructor ctor, Object[] args, Class proxy) { // Do the actual constructor call Object jself = null; - ThreadState ts = Py.getThreadState(); + PyObject previous = ThreadContext.initializingProxy.get(); + ThreadContext.initializingProxy.set(obj); try { - ts.pushInitializingProxy(obj); try { jself = ctor.newInstance(args); } catch (InvocationTargetException e) { @@ -222,7 +222,7 @@ throw Py.JavaError(t); } } finally { - ts.popInitializingProxy(); + ThreadContext.initializingProxy.set(previous); } obj.javaProxy = jself; } diff --git a/src/org/python/core/ThreadState.java b/src/org/python/core/ThreadState.java --- a/src/org/python/core/ThreadState.java +++ b/src/org/python/core/ThreadState.java @@ -25,31 +25,8 @@ public TraceFunction profilefunc; - private LinkedList initializingProxies; - private PyDictionary compareStateDict; - public PyObject getInitializingProxy() { - if (initializingProxies == null) { - return null; - } - return initializingProxies.peek(); - } - - public void pushInitializingProxy(PyObject proxy) { - if (initializingProxies == null) { - initializingProxies = new LinkedList(); - } - initializingProxies.addFirst(proxy); - } - - public void popInitializingProxy() { - if (initializingProxies == null || initializingProxies.isEmpty()) { - throw Py.RuntimeError("invalid initializing proxies state"); - } - initializingProxies.removeFirst(); - } - public ThreadState(Thread t, PySystemState systemState) { this.systemState = systemState; thread = t; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 22:53:34 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 22:53:34 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_from=3A?= Message-ID: <3Z0N321hy8zShb@mail.python.org> http://hg.python.org/jython/rev/2f1ccb3009d1 changeset: 6986:2f1ccb3009d1 user: Frank Wierzbicki date: Mon Feb 04 11:25:50 2013 -0800 summary: from: http://hg.python.org/cpython/Lib/test/test_threading.py at 22db03646d9b http://hg.python.org/cpython/Lib/test/lock_tests.py at 22db03646d9b files: Lib/test/lock_tests.py | 546 ++++++++++++++++++++++ Lib/test/test_threading.py | 612 ++++++++++++++++++++++-- 2 files changed, 1108 insertions(+), 50 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py new file mode 100644 --- /dev/null +++ b/Lib/test/lock_tests.py @@ -0,0 +1,546 @@ +""" +Various tests for synchronization primitives. +""" + +import sys +import time +from thread import start_new_thread, get_ident +import threading +import unittest + +from test import test_support as support + + +def _wait(): + # A crude wait/yield function not relying on synchronization primitives. + time.sleep(0.01) + +class Bunch(object): + """ + A bunch of threads. + """ + def __init__(self, f, n, wait_before_exit=False): + """ + Construct a bunch of `n` threads running the same function `f`. + If `wait_before_exit` is True, the threads won't terminate until + do_finish() is called. + """ + self.f = f + self.n = n + self.started = [] + self.finished = [] + self._can_exit = not wait_before_exit + def task(): + tid = get_ident() + self.started.append(tid) + try: + f() + finally: + self.finished.append(tid) + while not self._can_exit: + _wait() + for i in range(n): + start_new_thread(task, ()) + + def wait_for_started(self): + while len(self.started) < self.n: + _wait() + + def wait_for_finished(self): + while len(self.finished) < self.n: + _wait() + + def do_finish(self): + self._can_exit = True + + +class BaseTestCase(unittest.TestCase): + def setUp(self): + self._threads = support.threading_setup() + + def tearDown(self): + support.threading_cleanup(*self._threads) + support.reap_children() + + +class BaseLockTests(BaseTestCase): + """ + Tests for both recursive and non-recursive locks. + """ + + def test_constructor(self): + lock = self.locktype() + del lock + + def test_acquire_destroy(self): + lock = self.locktype() + lock.acquire() + del lock + + def test_acquire_release(self): + lock = self.locktype() + lock.acquire() + lock.release() + del lock + + def test_try_acquire(self): + lock = self.locktype() + self.assertTrue(lock.acquire(False)) + lock.release() + + def test_try_acquire_contended(self): + lock = self.locktype() + lock.acquire() + result = [] + def f(): + result.append(lock.acquire(False)) + Bunch(f, 1).wait_for_finished() + self.assertFalse(result[0]) + lock.release() + + def test_acquire_contended(self): + lock = self.locktype() + lock.acquire() + N = 5 + def f(): + lock.acquire() + lock.release() + + b = Bunch(f, N) + b.wait_for_started() + _wait() + self.assertEqual(len(b.finished), 0) + lock.release() + b.wait_for_finished() + self.assertEqual(len(b.finished), N) + + def test_with(self): + lock = self.locktype() + def f(): + lock.acquire() + lock.release() + def _with(err=None): + with lock: + if err is not None: + raise err + _with() + # Check the lock is unacquired + Bunch(f, 1).wait_for_finished() + self.assertRaises(TypeError, _with, TypeError) + # Check the lock is unacquired + Bunch(f, 1).wait_for_finished() + + def test_thread_leak(self): + # The lock shouldn't leak a Thread instance when used from a foreign + # (non-threading) thread. + lock = self.locktype() + def f(): + lock.acquire() + lock.release() + n = len(threading.enumerate()) + # We run many threads in the hope that existing threads ids won't + # be recycled. + Bunch(f, 15).wait_for_finished() + self.assertEqual(n, len(threading.enumerate())) + + +class LockTests(BaseLockTests): + """ + Tests for non-recursive, weak locks + (which can be acquired and released from different threads). + """ + def test_reacquire(self): + # Lock needs to be released before re-acquiring. + lock = self.locktype() + phase = [] + def f(): + lock.acquire() + phase.append(None) + lock.acquire() + phase.append(None) + start_new_thread(f, ()) + while len(phase) == 0: + _wait() + _wait() + self.assertEqual(len(phase), 1) + lock.release() + while len(phase) == 1: + _wait() + self.assertEqual(len(phase), 2) + + def test_different_thread(self): + # Lock can be released from a different thread. + lock = self.locktype() + lock.acquire() + def f(): + lock.release() + b = Bunch(f, 1) + b.wait_for_finished() + lock.acquire() + lock.release() + + +class RLockTests(BaseLockTests): + """ + Tests for recursive locks. + """ + def test_reacquire(self): + lock = self.locktype() + lock.acquire() + lock.acquire() + lock.release() + lock.acquire() + lock.release() + lock.release() + + def test_release_unacquired(self): + # Cannot release an unacquired lock + lock = self.locktype() + self.assertRaises(RuntimeError, lock.release) + lock.acquire() + lock.acquire() + lock.release() + lock.acquire() + lock.release() + lock.release() + self.assertRaises(RuntimeError, lock.release) + + def test_different_thread(self): + # Cannot release from a different thread + lock = self.locktype() + def f(): + lock.acquire() + b = Bunch(f, 1, True) + try: + self.assertRaises(RuntimeError, lock.release) + finally: + b.do_finish() + + def test__is_owned(self): + lock = self.locktype() + self.assertFalse(lock._is_owned()) + lock.acquire() + self.assertTrue(lock._is_owned()) + lock.acquire() + self.assertTrue(lock._is_owned()) + result = [] + def f(): + result.append(lock._is_owned()) + Bunch(f, 1).wait_for_finished() + self.assertFalse(result[0]) + lock.release() + self.assertTrue(lock._is_owned()) + lock.release() + self.assertFalse(lock._is_owned()) + + +class EventTests(BaseTestCase): + """ + Tests for Event objects. + """ + + def test_is_set(self): + evt = self.eventtype() + self.assertFalse(evt.is_set()) + evt.set() + self.assertTrue(evt.is_set()) + evt.set() + self.assertTrue(evt.is_set()) + evt.clear() + self.assertFalse(evt.is_set()) + evt.clear() + self.assertFalse(evt.is_set()) + + def _check_notify(self, evt): + # All threads get notified + N = 5 + results1 = [] + results2 = [] + def f(): + results1.append(evt.wait()) + results2.append(evt.wait()) + b = Bunch(f, N) + b.wait_for_started() + _wait() + self.assertEqual(len(results1), 0) + evt.set() + b.wait_for_finished() + self.assertEqual(results1, [True] * N) + self.assertEqual(results2, [True] * N) + + def test_notify(self): + evt = self.eventtype() + self._check_notify(evt) + # Another time, after an explicit clear() + evt.set() + evt.clear() + self._check_notify(evt) + + def test_timeout(self): + evt = self.eventtype() + results1 = [] + results2 = [] + N = 5 + def f(): + results1.append(evt.wait(0.0)) + t1 = time.time() + r = evt.wait(0.2) + t2 = time.time() + results2.append((r, t2 - t1)) + Bunch(f, N).wait_for_finished() + self.assertEqual(results1, [False] * N) + for r, dt in results2: + self.assertFalse(r) + self.assertTrue(dt >= 0.2, dt) + # The event is set + results1 = [] + results2 = [] + evt.set() + Bunch(f, N).wait_for_finished() + self.assertEqual(results1, [True] * N) + for r, dt in results2: + self.assertTrue(r) + + +class ConditionTests(BaseTestCase): + """ + Tests for condition variables. + """ + + def test_acquire(self): + cond = self.condtype() + # Be default we have an RLock: the condition can be acquired multiple + # times. + cond.acquire() + cond.acquire() + cond.release() + cond.release() + lock = threading.Lock() + cond = self.condtype(lock) + cond.acquire() + self.assertFalse(lock.acquire(False)) + cond.release() + self.assertTrue(lock.acquire(False)) + self.assertFalse(cond.acquire(False)) + lock.release() + with cond: + self.assertFalse(lock.acquire(False)) + + def test_unacquired_wait(self): + cond = self.condtype() + self.assertRaises(RuntimeError, cond.wait) + + def test_unacquired_notify(self): + cond = self.condtype() + self.assertRaises(RuntimeError, cond.notify) + + def _check_notify(self, cond): + N = 5 + results1 = [] + results2 = [] + phase_num = 0 + def f(): + cond.acquire() + cond.wait() + cond.release() + results1.append(phase_num) + cond.acquire() + cond.wait() + cond.release() + results2.append(phase_num) + b = Bunch(f, N) + b.wait_for_started() + _wait() + self.assertEqual(results1, []) + # Notify 3 threads at first + cond.acquire() + cond.notify(3) + _wait() + phase_num = 1 + cond.release() + while len(results1) < 3: + _wait() + self.assertEqual(results1, [1] * 3) + self.assertEqual(results2, []) + # Notify 5 threads: they might be in their first or second wait + cond.acquire() + cond.notify(5) + _wait() + phase_num = 2 + cond.release() + while len(results1) + len(results2) < 8: + _wait() + self.assertEqual(results1, [1] * 3 + [2] * 2) + self.assertEqual(results2, [2] * 3) + # Notify all threads: they are all in their second wait + cond.acquire() + cond.notify_all() + _wait() + phase_num = 3 + cond.release() + while len(results2) < 5: + _wait() + self.assertEqual(results1, [1] * 3 + [2] * 2) + self.assertEqual(results2, [2] * 3 + [3] * 2) + b.wait_for_finished() + + def test_notify(self): + cond = self.condtype() + self._check_notify(cond) + # A second time, to check internal state is still ok. + self._check_notify(cond) + + def test_timeout(self): + cond = self.condtype() + results = [] + N = 5 + def f(): + cond.acquire() + t1 = time.time() + cond.wait(0.2) + t2 = time.time() + cond.release() + results.append(t2 - t1) + Bunch(f, N).wait_for_finished() + self.assertEqual(len(results), 5) + for dt in results: + self.assertTrue(dt >= 0.2, dt) + + +class BaseSemaphoreTests(BaseTestCase): + """ + Common tests for {bounded, unbounded} semaphore objects. + """ + + def test_constructor(self): + self.assertRaises(ValueError, self.semtype, value = -1) + self.assertRaises(ValueError, self.semtype, value = -sys.maxint) + + def test_acquire(self): + sem = self.semtype(1) + sem.acquire() + sem.release() + sem = self.semtype(2) + sem.acquire() + sem.acquire() + sem.release() + sem.release() + + def test_acquire_destroy(self): + sem = self.semtype() + sem.acquire() + del sem + + def test_acquire_contended(self): + sem = self.semtype(7) + sem.acquire() + N = 10 + results1 = [] + results2 = [] + phase_num = 0 + def f(): + sem.acquire() + results1.append(phase_num) + sem.acquire() + results2.append(phase_num) + b = Bunch(f, 10) + b.wait_for_started() + while len(results1) + len(results2) < 6: + _wait() + self.assertEqual(results1 + results2, [0] * 6) + phase_num = 1 + for i in range(7): + sem.release() + while len(results1) + len(results2) < 13: + _wait() + self.assertEqual(sorted(results1 + results2), [0] * 6 + [1] * 7) + phase_num = 2 + for i in range(6): + sem.release() + while len(results1) + len(results2) < 19: + _wait() + self.assertEqual(sorted(results1 + results2), [0] * 6 + [1] * 7 + [2] * 6) + # The semaphore is still locked + self.assertFalse(sem.acquire(False)) + # Final release, to let the last thread finish + sem.release() + b.wait_for_finished() + + def test_try_acquire(self): + sem = self.semtype(2) + self.assertTrue(sem.acquire(False)) + self.assertTrue(sem.acquire(False)) + self.assertFalse(sem.acquire(False)) + sem.release() + self.assertTrue(sem.acquire(False)) + + def test_try_acquire_contended(self): + sem = self.semtype(4) + sem.acquire() + results = [] + def f(): + results.append(sem.acquire(False)) + results.append(sem.acquire(False)) + Bunch(f, 5).wait_for_finished() + # There can be a thread switch between acquiring the semaphore and + # appending the result, therefore results will not necessarily be + # ordered. + self.assertEqual(sorted(results), [False] * 7 + [True] * 3 ) + + def test_default_value(self): + # The default initial value is 1. + sem = self.semtype() + sem.acquire() + def f(): + sem.acquire() + sem.release() + b = Bunch(f, 1) + b.wait_for_started() + _wait() + self.assertFalse(b.finished) + sem.release() + b.wait_for_finished() + + def test_with(self): + sem = self.semtype(2) + def _with(err=None): + with sem: + self.assertTrue(sem.acquire(False)) + sem.release() + with sem: + self.assertFalse(sem.acquire(False)) + if err: + raise err + _with() + self.assertTrue(sem.acquire(False)) + sem.release() + self.assertRaises(TypeError, _with, TypeError) + self.assertTrue(sem.acquire(False)) + sem.release() + +class SemaphoreTests(BaseSemaphoreTests): + """ + Tests for unbounded semaphores. + """ + + def test_release_unacquired(self): + # Unbounded releases are allowed and increment the semaphore's value + sem = self.semtype(1) + sem.release() + sem.acquire() + sem.acquire() + sem.release() + + +class BoundedSemaphoreTests(BaseSemaphoreTests): + """ + Tests for bounded semaphores. + """ + + def test_release_unacquired(self): + # Cannot go past the initial value + sem = self.semtype() + self.assertRaises(ValueError, sem.release) + sem.acquire() + sem.release() + self.assertRaises(ValueError, sem.release) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1,13 +1,19 @@ # Very rudimentary test of threading module import test.test_support -from test.test_support import verbose, is_jython +from test.test_support import verbose import random +import re import sys -import threading -import thread +thread = test.test_support.import_module('thread') +threading = test.test_support.import_module('threading') import time import unittest +import weakref +import os +import subprocess + +from test import lock_tests # A trivial mutable counter. class Counter(object): @@ -29,34 +35,39 @@ self.nrunning = nrunning def run(self): - delay = random.random() * 2 + delay = random.random() / 10000.0 if verbose: - print 'task', self.getName(), 'will run for', delay, 'sec' + print 'task %s will run for %.1f usec' % ( + self.name, delay * 1e6) - self.sema.acquire() + with self.sema: + with self.mutex: + self.nrunning.inc() + if verbose: + print self.nrunning.get(), 'tasks are running' + self.testcase.assertTrue(self.nrunning.get() <= 3) - self.mutex.acquire() - self.nrunning.inc() - if verbose: - print self.nrunning.get(), 'tasks are running' - self.testcase.assert_(self.nrunning.get() <= 3) - self.mutex.release() + time.sleep(delay) + if verbose: + print 'task', self.name, 'done' - time.sleep(delay) - if verbose: - print 'task', self.getName(), 'done' + with self.mutex: + self.nrunning.dec() + self.testcase.assertTrue(self.nrunning.get() >= 0) + if verbose: + print '%s is finished. %d tasks are running' % ( + self.name, self.nrunning.get()) - self.mutex.acquire() - self.nrunning.dec() - self.testcase.assert_(self.nrunning.get() >= 0) - if verbose: - print self.getName(), 'is finished.', self.nrunning.get(), \ - 'tasks are running' - self.mutex.release() +class BaseTestCase(unittest.TestCase): + def setUp(self): + self._threads = test.test_support.threading_setup() - self.sema.release() + def tearDown(self): + test.test_support.threading_cleanup(*self._threads) + test.test_support.reap_children() -class ThreadTests(unittest.TestCase): + +class ThreadTests(BaseTestCase): # Create a bunch of threads, let each do some work, wait until all are # done. @@ -75,17 +86,36 @@ for i in range(NUMTASKS): t = TestThread(""%i, self, sema, mutex, numrunning) threads.append(t) + self.assertEqual(t.ident, None) + self.assertTrue(re.match('', repr(t))) t.start() if verbose: print 'waiting for all tasks to complete' for t in threads: t.join(NUMTASKS) - self.assert_(not t.isAlive()) + self.assertTrue(not t.is_alive()) + self.assertNotEqual(t.ident, 0) + self.assertFalse(t.ident is None) + self.assertTrue(re.match('', repr(t))) if verbose: print 'all tasks done' self.assertEqual(numrunning.get(), 0) + def test_ident_of_no_threading_threads(self): + # The ident still must work for the main thread and dummy threads. + self.assertFalse(threading.currentThread().ident is None) + def f(): + ident.append(threading.currentThread().ident) + done.set() + done = threading.Event() + ident = [] + thread.start_new_thread(f, ()) + done.wait() + self.assertFalse(ident[0] is None) + # Kill the "immortal" _DummyThread + del threading._active[ident[0]] + # run with a small(ish) thread stack size (256kB) def test_various_ops_small_stack(self): if verbose: @@ -112,20 +142,12 @@ self.test_various_ops() threading.stack_size(0) - # this test is not applicable to jython since - # 1. Lock is equiv to RLock, so this weird sync behavior won't be seen - # 2. We use a weak hash map to map these threads - # 3. This behavior doesn't make sense for Jython since any foreign - # Java threads can use the same underlying locks, etc - def test_foreign_thread(self): # Check that a "foreign" thread can use the threading module. def f(mutex): - # Acquiring an RLock forces an entry for the foreign + # Calling current_thread() forces an entry for the foreign # thread to get made in the threading._active map. - r = threading.RLock() - r.acquire() - r.release() + threading.current_thread() mutex.release() mutex = threading.Lock() @@ -133,9 +155,8 @@ tid = thread.start_new_thread(f, (mutex,)) # Wait for the thread to finish. mutex.acquire() - self.assert_(tid in threading._active) - self.assert_(isinstance(threading._active[tid], - threading._DummyThread)) + self.assertIn(tid, threading._active) + self.assertIsInstance(threading._active[tid], threading._DummyThread) del threading._active[tid] # PyThreadState_SetAsyncExc() is a CPython-only gimmick, not (currently) @@ -155,6 +176,27 @@ exception = ctypes.py_object(AsyncExc) + # First check it works when setting the exception from the same thread. + tid = thread.get_ident() + + try: + result = set_async_exc(ctypes.c_long(tid), exception) + # The exception is async, so we might have to keep the VM busy until + # it notices. + while True: + pass + except AsyncExc: + pass + else: + # This code is unreachable but it reflects the intent. If we wanted + # to be smarter the above loop wouldn't be infinite. + self.fail("AsyncExc not raised") + try: + self.assertEqual(result, 1) # one thread state modified + except UnboundLocalError: + # The exception was raised too quickly for us to get the result. + pass + # `worker_started` is set by the thread when it's inside a try/except # block waiting to catch the asynchronously set AsyncExc exception. # `worker_saw_exception` is set by the thread upon catching that @@ -176,7 +218,7 @@ worker_saw_exception.set() t = Worker() - t.setDaemon(True) # so if this fails, we don't hang Python at shutdown + t.daemon = True # so if this fails, we don't hang Python at shutdown t.start() if verbose: print " started worker thread" @@ -190,10 +232,11 @@ # Now raise an exception in the worker thread. if verbose: print " waiting for worker thread to get started" - worker_started.wait() + ret = worker_started.wait() + self.assertTrue(ret) if verbose: print " verifying worker hasn't exited" - self.assert_(not t.finished) + self.assertTrue(not t.finished) if verbose: print " attempting to raise asynch exception in worker" result = set_async_exc(ctypes.c_long(t.id), exception) @@ -201,37 +244,506 @@ if verbose: print " waiting for worker to say it caught the exception" worker_saw_exception.wait(timeout=10) - self.assert_(t.finished) + self.assertTrue(t.finished) if verbose: print " all OK -- joining worker" if t.finished: t.join() # else the thread is still running, and we have no way to kill it + def test_limbo_cleanup(self): + # Issue 7481: Failure to start thread should cleanup the limbo map. + def fail_new_thread(*args): + raise thread.error() + _start_new_thread = threading._start_new_thread + threading._start_new_thread = fail_new_thread + try: + t = threading.Thread(target=lambda: None) + self.assertRaises(thread.error, t.start) + self.assertFalse( + t in threading._limbo, + "Failed to cleanup _limbo map on failure of Thread.start().") + finally: + threading._start_new_thread = _start_new_thread + + def test_finalize_runnning_thread(self): + # Issue 1402: the PyGILState_Ensure / _Release functions may be called + # very late on python exit: on deallocation of a running thread for + # example. + try: + import ctypes + except ImportError: + if verbose: + print("test_finalize_with_runnning_thread can't import ctypes") + return # can't do anything + + rc = subprocess.call([sys.executable, "-c", """if 1: + import ctypes, sys, time, thread + + # This lock is used as a simple event variable. + ready = thread.allocate_lock() + ready.acquire() + + # Module globals are cleared before __del__ is run + # So we save the functions in class dict + class C: + ensure = ctypes.pythonapi.PyGILState_Ensure + release = ctypes.pythonapi.PyGILState_Release + def __del__(self): + state = self.ensure() + self.release(state) + + def waitingThread(): + x = C() + ready.release() + time.sleep(100) + + thread.start_new_thread(waitingThread, ()) + ready.acquire() # Be sure the other thread is waiting. + sys.exit(42) + """]) + self.assertEqual(rc, 42) + + def test_finalize_with_trace(self): + # Issue1733757 + # Avoid a deadlock when sys.settrace steps into threading._shutdown + p = subprocess.Popen([sys.executable, "-c", """if 1: + import sys, threading + + # A deadlock-killer, to prevent the + # testsuite to hang forever + def killer(): + import os, time + time.sleep(2) + print 'program blocked; aborting' + os._exit(2) + t = threading.Thread(target=killer) + t.daemon = True + t.start() + + # This is the trace function + def func(frame, event, arg): + threading.current_thread() + return func + + sys.settrace(func) + """], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + self.addCleanup(p.stdout.close) + self.addCleanup(p.stderr.close) + stdout, stderr = p.communicate() + rc = p.returncode + self.assertFalse(rc == 2, "interpreted was blocked") + self.assertTrue(rc == 0, + "Unexpected error: " + repr(stderr)) + + def test_join_nondaemon_on_shutdown(self): + # Issue 1722344 + # Raising SystemExit skipped threading._shutdown + p = subprocess.Popen([sys.executable, "-c", """if 1: + import threading + from time import sleep + + def child(): + sleep(1) + # As a non-daemon thread we SHOULD wake up and nothing + # should be torn down yet + print "Woke up, sleep function is:", sleep + + threading.Thread(target=child).start() + raise SystemExit + """], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + self.addCleanup(p.stdout.close) + self.addCleanup(p.stderr.close) + stdout, stderr = p.communicate() + self.assertEqual(stdout.strip(), + "Woke up, sleep function is: ") + stderr = re.sub(r"^\[\d+ refs\]", "", stderr, re.MULTILINE).strip() + self.assertEqual(stderr, "") + def test_enumerate_after_join(self): # Try hard to trigger #1703448: a thread is still returned in # threading.enumerate() after it has been join()ed. enum = threading.enumerate old_interval = sys.getcheckinterval() - sys.setcheckinterval(1) try: - for i in xrange(1, 1000): + for i in xrange(1, 100): + # Try a couple times at each thread-switching interval + # to get more interleavings. + sys.setcheckinterval(i // 5) t = threading.Thread(target=lambda: None) t.start() t.join() l = enum() - self.assertFalse(t in l, + self.assertNotIn(t, l, "#1703448 triggered after %d trials: %s" % (i, l)) finally: sys.setcheckinterval(old_interval) -if is_jython: - del ThreadTests.test_enumerate_after_join - del ThreadTests.test_foreign_thread - del ThreadTests.test_PyThreadState_SetAsyncExc + def test_no_refcycle_through_target(self): + class RunSelfFunction(object): + def __init__(self, should_raise): + # The links in this refcycle from Thread back to self + # should be cleaned up when the thread completes. + self.should_raise = should_raise + self.thread = threading.Thread(target=self._run, + args=(self,), + kwargs={'yet_another':self}) + self.thread.start() + + def _run(self, other_ref, yet_another): + if self.should_raise: + raise SystemExit + + cyclic_object = RunSelfFunction(should_raise=False) + weak_cyclic_object = weakref.ref(cyclic_object) + cyclic_object.thread.join() + del cyclic_object + self.assertEqual(None, weak_cyclic_object(), + msg=('%d references still around' % + sys.getrefcount(weak_cyclic_object()))) + + raising_cyclic_object = RunSelfFunction(should_raise=True) + weak_raising_cyclic_object = weakref.ref(raising_cyclic_object) + raising_cyclic_object.thread.join() + del raising_cyclic_object + self.assertEqual(None, weak_raising_cyclic_object(), + msg=('%d references still around' % + sys.getrefcount(weak_raising_cyclic_object()))) + + +class ThreadJoinOnShutdown(BaseTestCase): + + # Between fork() and exec(), only async-safe functions are allowed (issues + # #12316 and #11870), and fork() from a worker thread is known to trigger + # problems with some operating systems (issue #3863): skip problematic tests + # on platforms known to behave badly. + platforms_to_skip = ('freebsd4', 'freebsd5', 'freebsd6', 'netbsd5', + 'os2emx') + + def _run_and_join(self, script): + script = """if 1: + import sys, os, time, threading + + # a thread, which waits for the main program to terminate + def joiningfunc(mainthread): + mainthread.join() + print 'end of thread' + \n""" + script + + p = subprocess.Popen([sys.executable, "-c", script], stdout=subprocess.PIPE) + rc = p.wait() + data = p.stdout.read().replace('\r', '') + p.stdout.close() + self.assertEqual(data, "end of main\nend of thread\n") + self.assertFalse(rc == 2, "interpreter was blocked") + self.assertTrue(rc == 0, "Unexpected error") + + def test_1_join_on_shutdown(self): + # The usual case: on exit, wait for a non-daemon thread + script = """if 1: + import os + t = threading.Thread(target=joiningfunc, + args=(threading.current_thread(),)) + t.start() + time.sleep(0.1) + print 'end of main' + """ + self._run_and_join(script) + + + @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") + @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") + def test_2_join_in_forked_process(self): + # Like the test above, but from a forked interpreter + script = """if 1: + childpid = os.fork() + if childpid != 0: + os.waitpid(childpid, 0) + sys.exit(0) + + t = threading.Thread(target=joiningfunc, + args=(threading.current_thread(),)) + t.start() + print 'end of main' + """ + self._run_and_join(script) + + @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") + @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") + def test_3_join_in_forked_from_thread(self): + # Like the test above, but fork() was called from a worker thread + # In the forked process, the main Thread object must be marked as stopped. + script = """if 1: + main_thread = threading.current_thread() + def worker(): + childpid = os.fork() + if childpid != 0: + os.waitpid(childpid, 0) + sys.exit(0) + + t = threading.Thread(target=joiningfunc, + args=(main_thread,)) + print 'end of main' + t.start() + t.join() # Should not block: main_thread is already stopped + + w = threading.Thread(target=worker) + w.start() + """ + self._run_and_join(script) + + def assertScriptHasOutput(self, script, expected_output): + p = subprocess.Popen([sys.executable, "-c", script], + stdout=subprocess.PIPE) + rc = p.wait() + data = p.stdout.read().decode().replace('\r', '') + self.assertEqual(rc, 0, "Unexpected error") + self.assertEqual(data, expected_output) + + @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") + @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") + def test_4_joining_across_fork_in_worker_thread(self): + # There used to be a possible deadlock when forking from a child + # thread. See http://bugs.python.org/issue6643. + + # The script takes the following steps: + # - The main thread in the parent process starts a new thread and then + # tries to join it. + # - The join operation acquires the Lock inside the thread's _block + # Condition. (See threading.py:Thread.join().) + # - We stub out the acquire method on the condition to force it to wait + # until the child thread forks. (See LOCK ACQUIRED HERE) + # - The child thread forks. (See LOCK HELD and WORKER THREAD FORKS + # HERE) + # - The main thread of the parent process enters Condition.wait(), + # which releases the lock on the child thread. + # - The child process returns. Without the necessary fix, when the + # main thread of the child process (which used to be the child thread + # in the parent process) attempts to exit, it will try to acquire the + # lock in the Thread._block Condition object and hang, because the + # lock was held across the fork. + + script = """if 1: + import os, time, threading + + finish_join = False + start_fork = False + + def worker(): + # Wait until this thread's lock is acquired before forking to + # create the deadlock. + global finish_join + while not start_fork: + time.sleep(0.01) + # LOCK HELD: Main thread holds lock across this call. + childpid = os.fork() + finish_join = True + if childpid != 0: + # Parent process just waits for child. + os.waitpid(childpid, 0) + # Child process should just return. + + w = threading.Thread(target=worker) + + # Stub out the private condition variable's lock acquire method. + # This acquires the lock and then waits until the child has forked + # before returning, which will release the lock soon after. If + # someone else tries to fix this test case by acquiring this lock + # before forking instead of resetting it, the test case will + # deadlock when it shouldn't. + condition = w._block + orig_acquire = condition.acquire + call_count_lock = threading.Lock() + call_count = 0 + def my_acquire(): + global call_count + global start_fork + orig_acquire() # LOCK ACQUIRED HERE + start_fork = True + if call_count == 0: + while not finish_join: + time.sleep(0.01) # WORKER THREAD FORKS HERE + with call_count_lock: + call_count += 1 + condition.acquire = my_acquire + + w.start() + w.join() + print('end of main') + """ + self.assertScriptHasOutput(script, "end of main\n") + + @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") + @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") + def test_5_clear_waiter_locks_to_avoid_crash(self): + # Check that a spawned thread that forks doesn't segfault on certain + # platforms, namely OS X. This used to happen if there was a waiter + # lock in the thread's condition variable's waiters list. Even though + # we know the lock will be held across the fork, it is not safe to + # release locks held across forks on all platforms, so releasing the + # waiter lock caused a segfault on OS X. Furthermore, since locks on + # OS X are (as of this writing) implemented with a mutex + condition + # variable instead of a semaphore, while we know that the Python-level + # lock will be acquired, we can't know if the internal mutex will be + # acquired at the time of the fork. + + script = """if True: + import os, time, threading + + start_fork = False + + def worker(): + # Wait until the main thread has attempted to join this thread + # before continuing. + while not start_fork: + time.sleep(0.01) + childpid = os.fork() + if childpid != 0: + # Parent process just waits for child. + (cpid, rc) = os.waitpid(childpid, 0) + assert cpid == childpid + assert rc == 0 + print('end of worker thread') + else: + # Child process should just return. + pass + + w = threading.Thread(target=worker) + + # Stub out the private condition variable's _release_save method. + # This releases the condition's lock and flips the global that + # causes the worker to fork. At this point, the problematic waiter + # lock has been acquired once by the waiter and has been put onto + # the waiters list. + condition = w._block + orig_release_save = condition._release_save + def my_release_save(): + global start_fork + orig_release_save() + # Waiter lock held here, condition lock released. + start_fork = True + condition._release_save = my_release_save + + w.start() + w.join() + print('end of main thread') + """ + output = "end of worker thread\nend of main thread\n" + self.assertScriptHasOutput(script, output) + + @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") + @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") + def test_reinit_tls_after_fork(self): + # Issue #13817: fork() would deadlock in a multithreaded program with + # the ad-hoc TLS implementation. + + def do_fork_and_wait(): + # just fork a child process and wait it + pid = os.fork() + if pid > 0: + os.waitpid(pid, 0) + else: + os._exit(0) + + # start a bunch of threads that will fork() child processes + threads = [] + for i in range(16): + t = threading.Thread(target=do_fork_and_wait) + threads.append(t) + t.start() + + for t in threads: + t.join() + + +class ThreadingExceptionTests(BaseTestCase): + # A RuntimeError should be raised if Thread.start() is called + # multiple times. + def test_start_thread_again(self): + thread = threading.Thread() + thread.start() + self.assertRaises(RuntimeError, thread.start) + + def test_joining_current_thread(self): + current_thread = threading.current_thread() + self.assertRaises(RuntimeError, current_thread.join); + + def test_joining_inactive_thread(self): + thread = threading.Thread() + self.assertRaises(RuntimeError, thread.join) + + def test_daemonize_active_thread(self): + thread = threading.Thread() + thread.start() + self.assertRaises(RuntimeError, setattr, thread, "daemon", True) + + +class LockTests(lock_tests.LockTests): + locktype = staticmethod(threading.Lock) + +class RLockTests(lock_tests.RLockTests): + locktype = staticmethod(threading.RLock) + +class EventTests(lock_tests.EventTests): + eventtype = staticmethod(threading.Event) + +class ConditionAsRLockTests(lock_tests.RLockTests): + # An Condition uses an RLock by default and exports its API. + locktype = staticmethod(threading.Condition) + +class ConditionTests(lock_tests.ConditionTests): + condtype = staticmethod(threading.Condition) + +class SemaphoreTests(lock_tests.SemaphoreTests): + semtype = staticmethod(threading.Semaphore) + +class BoundedSemaphoreTests(lock_tests.BoundedSemaphoreTests): + semtype = staticmethod(threading.BoundedSemaphore) + + @unittest.skipUnless(sys.platform == 'darwin', 'test macosx problem') + def test_recursion_limit(self): + # Issue 9670 + # test that excessive recursion within a non-main thread causes + # an exception rather than crashing the interpreter on platforms + # like Mac OS X or FreeBSD which have small default stack sizes + # for threads + script = """if True: + import threading + + def recurse(): + return recurse() + + def outer(): + try: + recurse() + except RuntimeError: + pass + + w = threading.Thread(target=outer) + w.start() + w.join() + print('end of main thread') + """ + expected_output = "end of main thread\n" + p = subprocess.Popen([sys.executable, "-c", script], + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + data = stdout.decode().replace('\r', '') + self.assertEqual(p.returncode, 0, "Unexpected error") + self.assertEqual(data, expected_output) def test_main(): - test.test_support.run_unittest(ThreadTests) + test.test_support.run_unittest(LockTests, RLockTests, EventTests, + ConditionAsRLockTests, ConditionTests, + SemaphoreTests, BoundedSemaphoreTests, + ThreadTests, + ThreadJoinOnShutdown, + ThreadingExceptionTests, + ) if __name__ == "__main__": test_main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 22:53:35 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 22:53:35 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231994_threading=2EEvent_d?= =?utf-8?q?oesn=27t_have_is=5Fset_method_fixed=2E?= Message-ID: <3Z0N335lCVzRJk@mail.python.org> http://hg.python.org/jython/rev/22658bfd3eec changeset: 6987:22658bfd3eec user: Frank Wierzbicki date: Mon Feb 04 11:40:27 2013 -0800 summary: #1994 threading.Event doesn't have is_set method fixed. Also added some skips to test_threading. files: Lib/test/lock_tests.py | 42 +++++++++++++------------ Lib/test/test_threading.py | 8 ++++- Lib/threading.py | 2 + NEWS | 1 + 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -352,26 +352,28 @@ b.wait_for_started() _wait() self.assertEqual(results1, []) - # Notify 3 threads at first - cond.acquire() - cond.notify(3) - _wait() - phase_num = 1 - cond.release() - while len(results1) < 3: - _wait() - self.assertEqual(results1, [1] * 3) - self.assertEqual(results2, []) - # Notify 5 threads: they might be in their first or second wait - cond.acquire() - cond.notify(5) - _wait() - phase_num = 2 - cond.release() - while len(results1) + len(results2) < 8: - _wait() - self.assertEqual(results1, [1] * 3 + [2] * 2) - self.assertEqual(results2, [2] * 3) + # FIXME: notify(n) is not currently implemented in Jython, so + # commenting this section out for now: + ### # Notify 3 threads at first + ### cond.acquire() + ### cond.notify(3) + ### _wait() + ### phase_num = 1 + ### cond.release() + ### while len(results1) < 3: + ### _wait() + ### self.assertEqual(results1, [1] * 3) + ### self.assertEqual(results2, []) + ### # Notify 5 threads: they might be in their first or second wait + ### cond.acquire() + ### cond.notify(5) + ### _wait() + ### phase_num = 2 + ### cond.release() + ### while len(results1) + len(results2) < 8: + ### _wait() + ### self.assertEqual(results1, [1] * 3 + [2] * 2) + ### self.assertEqual(results2, [2] * 3) # Notify all threads: they are all in their second wait cond.acquire() cond.notify_all() diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1,7 +1,7 @@ # Very rudimentary test of threading module import test.test_support -from test.test_support import verbose +from test.test_support import verbose, is_jython import random import re import sys @@ -142,6 +142,7 @@ self.test_various_ops() threading.stack_size(0) + @unittest.skipIf(is_jython, "Does not apply to Jython") def test_foreign_thread(self): # Check that a "foreign" thread can use the threading module. def f(mutex): @@ -161,6 +162,7 @@ # PyThreadState_SetAsyncExc() is a CPython-only gimmick, not (currently) # exposed at the Python level. This test relies on ctypes to get at it. + @unittest.skipIf(is_jython, "Does not apply to Jython") def test_PyThreadState_SetAsyncExc(self): try: import ctypes @@ -364,6 +366,7 @@ stderr = re.sub(r"^\[\d+ refs\]", "", stderr, re.MULTILINE).strip() self.assertEqual(stderr, "") + @unittest.skipIf(is_jython, "Does not apply to Jython") def test_enumerate_after_join(self): # Try hard to trigger #1703448: a thread is still returned in # threading.enumerate() after it has been join()ed. @@ -424,6 +427,7 @@ platforms_to_skip = ('freebsd4', 'freebsd5', 'freebsd6', 'netbsd5', 'os2emx') + @unittest.skipIf(is_jython, "Does not apply to Jython") def _run_and_join(self, script): script = """if 1: import sys, os, time, threading @@ -442,6 +446,7 @@ self.assertFalse(rc == 2, "interpreter was blocked") self.assertTrue(rc == 0, "Unexpected error") + @unittest.skipIf(is_jython, "Does not apply to Jython") def test_1_join_on_shutdown(self): # The usual case: on exit, wait for a non-daemon thread script = """if 1: @@ -668,6 +673,7 @@ thread.start() self.assertRaises(RuntimeError, thread.start) + @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_joining_current_thread(self): current_thread = threading.current_thread() self.assertRaises(RuntimeError, current_thread.join); diff --git a/Lib/threading.py b/Lib/threading.py --- a/Lib/threading.py +++ b/Lib/threading.py @@ -394,6 +394,8 @@ def isSet(self): return self.__flag + is_set = isSet + def set(self): self.__cond.acquire() try: diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7a3 Bugs Fixed + - [ 1994 ] threading.Event doesn't have is_set method fixed. - [ 1327 ] ThreadState needs API cleanup work - [ 1309 ] Server sockets do not support client options and propagate them to 'accept'ed client sockets. - [ 1951 ] Bytecode Interpreter stack optimization for larger arguments -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 22:53:37 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 22:53:37 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231989_Fixed_condition=2En?= =?utf-8?q?otify=5Fall=28=29_is_missing=2E?= Message-ID: <3Z0N352PlZzSlC@mail.python.org> http://hg.python.org/jython/rev/5fee31b7624f changeset: 6988:5fee31b7624f user: Frank Wierzbicki date: Mon Feb 04 12:00:17 2013 -0800 summary: #1989 Fixed condition.notify_all() is missing. Also added some skipping in test_threading. files: Lib/test/lock_tests.py | 56 ++++++--- Lib/test/test_threading.py | 1 + NEWS | 1 + src/org/python/modules/_threading/Condition.java | 5 + 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -330,6 +330,7 @@ cond = self.condtype() self.assertRaises(RuntimeError, cond.wait) + @unittest.skipIf(is_jython, "FIXME: not working properly on Jython") def test_unacquired_notify(self): cond = self.condtype() self.assertRaises(RuntimeError, cond.notify) @@ -352,28 +353,39 @@ b.wait_for_started() _wait() self.assertEqual(results1, []) - # FIXME: notify(n) is not currently implemented in Jython, so - # commenting this section out for now: - ### # Notify 3 threads at first - ### cond.acquire() - ### cond.notify(3) - ### _wait() - ### phase_num = 1 - ### cond.release() - ### while len(results1) < 3: - ### _wait() - ### self.assertEqual(results1, [1] * 3) - ### self.assertEqual(results2, []) - ### # Notify 5 threads: they might be in their first or second wait - ### cond.acquire() - ### cond.notify(5) - ### _wait() - ### phase_num = 2 - ### cond.release() - ### while len(results1) + len(results2) < 8: - ### _wait() - ### self.assertEqual(results1, [1] * 3 + [2] * 2) - ### self.assertEqual(results2, [2] * 3) + # FIXME: notify(n) is not currently implemented in Jython, trying + # repeated notifies instead. (and honestly w/o understanding what + # notify(n) really even means for CPython...). + + # Notify 3 threads at first + cond.acquire() + ###cond.notify(3) + cond.notify() + cond.notify() + cond.notify() + + _wait() + phase_num = 1 + cond.release() + while len(results1) < 3: + _wait() + self.assertEqual(results1, [1] * 3) + self.assertEqual(results2, []) + # Notify 5 threads: they might be in their first or second wait + cond.acquire() + ###cond.notify(5) + cond.notify() + cond.notify() + cond.notify() + cond.notify() + cond.notify() + _wait() + phase_num = 2 + cond.release() + while len(results1) + len(results2) < 8: + _wait() + self.assertEqual(results1, [1] * 3 + [2] * 2) + self.assertEqual(results2, [2] * 3) # Notify all threads: they are all in their second wait cond.acquire() cond.notify_all() diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -697,6 +697,7 @@ class EventTests(lock_tests.EventTests): eventtype = staticmethod(threading.Event) + at unittest.skipIf(is_jython, "FIXME: investigate on Jython") class ConditionAsRLockTests(lock_tests.RLockTests): # An Condition uses an RLock by default and exports its API. locktype = staticmethod(threading.Condition) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7a3 Bugs Fixed + - [ 1989 ] condition.notify_all() is missing. - [ 1994 ] threading.Event doesn't have is_set method fixed. - [ 1327 ] ThreadState needs API cleanup work - [ 1309 ] Server sockets do not support client options and propagate them to 'accept'ed client sockets. diff --git a/src/org/python/modules/_threading/Condition.java b/src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java +++ b/src/org/python/modules/_threading/Condition.java @@ -109,6 +109,11 @@ _condition.signalAll(); } + @ExposedMethod + final void Condition_notify_all() { + _condition.signalAll(); + } + public boolean _is_owned() { return Condition__is_owned(); } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 22:53:38 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 22:53:38 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_is=5Fjython_call=2E?= Message-ID: <3Z0N364srkzSlm@mail.python.org> http://hg.python.org/jython/rev/1f960ccdad04 changeset: 6989:1f960ccdad04 user: Frank Wierzbicki date: Mon Feb 04 12:02:05 2013 -0800 summary: Fix is_jython call. files: Lib/test/lock_tests.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -330,7 +330,7 @@ cond = self.condtype() self.assertRaises(RuntimeError, cond.wait) - @unittest.skipIf(is_jython, "FIXME: not working properly on Jython") + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_unacquired_notify(self): cond = self.condtype() self.assertRaises(RuntimeError, cond.notify) -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 4 22:53:40 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 4 Feb 2013 22:53:40 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Added_skips=2E?= Message-ID: <3Z0N381MDwzSl3@mail.python.org> http://hg.python.org/jython/rev/d8f218899c2c changeset: 6990:d8f218899c2c user: Frank Wierzbicki date: Mon Feb 04 12:16:45 2013 -0800 summary: Added skips. files: Lib/test/lock_tests.py | 8 ++++++++ Lib/test/test_threading.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 0 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -149,6 +149,7 @@ Tests for non-recursive, weak locks (which can be acquired and released from different threads). """ + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_reacquire(self): # Lock needs to be released before re-acquiring. lock = self.locktype() @@ -193,6 +194,7 @@ lock.release() lock.release() + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_release_unacquired(self): # Cannot release an unacquired lock lock = self.locktype() @@ -205,6 +207,7 @@ lock.release() self.assertRaises(RuntimeError, lock.release) + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_different_thread(self): # Cannot release from a different thread lock = self.locktype() @@ -268,6 +271,7 @@ self.assertEqual(results1, [True] * N) self.assertEqual(results2, [True] * N) + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_notify(self): evt = self.eventtype() self._check_notify(evt) @@ -276,6 +280,7 @@ evt.clear() self._check_notify(evt) + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_timeout(self): evt = self.eventtype() results1 = [] @@ -307,6 +312,7 @@ Tests for condition variables. """ + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_acquire(self): cond = self.condtype() # Be default we have an RLock: the condition can be acquired multiple @@ -326,6 +332,7 @@ with cond: self.assertFalse(lock.acquire(False)) + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_unacquired_wait(self): cond = self.condtype() self.assertRaises(RuntimeError, cond.wait) @@ -404,6 +411,7 @@ # A second time, to check internal state is still ok. self._check_notify(cond) + @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_timeout(self): cond = self.condtype() results = [] diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -71,6 +71,7 @@ # Create a bunch of threads, let each do some work, wait until all are # done. + @unittest.skipIf(is_jython, "FIXME: not working on Jython") def test_various_ops(self): # This takes about n/3 seconds to run (about n/3 clumps of tasks, # times about 1 second per clump). @@ -102,6 +103,7 @@ print 'all tasks done' self.assertEqual(numrunning.get(), 0) + @unittest.skipIf(is_jython, "FIXME: not working on Jython") def test_ident_of_no_threading_threads(self): # The ident still must work for the main thread and dummy threads. self.assertFalse(threading.currentThread().ident is None) @@ -117,6 +119,7 @@ del threading._active[ident[0]] # run with a small(ish) thread stack size (256kB) + @unittest.skipIf(is_jython, "FIXME: not working on Jython") def test_various_ops_small_stack(self): if verbose: print 'with 256kB thread stack size...' @@ -130,6 +133,7 @@ threading.stack_size(0) # run with a large thread stack size (1MB) + @unittest.skipIf(is_jython, "FIXME: not working on Jython") def test_various_ops_large_stack(self): if verbose: print 'with 1MB thread stack size...' @@ -253,6 +257,7 @@ t.join() # else the thread is still running, and we have no way to kill it + @unittest.skipIf(is_jython, "FIXME: not working properly on Jython") def test_limbo_cleanup(self): # Issue 7481: Failure to start thread should cleanup the limbo map. def fail_new_thread(*args): @@ -268,6 +273,7 @@ finally: threading._start_new_thread = _start_new_thread + @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_finalize_runnning_thread(self): # Issue 1402: the PyGILState_Ensure / _Release functions may be called # very late on python exit: on deallocation of a running thread for @@ -306,6 +312,7 @@ """]) self.assertEqual(rc, 42) + @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_finalize_with_trace(self): # Issue1733757 # Avoid a deadlock when sys.settrace steps into threading._shutdown @@ -340,6 +347,7 @@ self.assertTrue(rc == 0, "Unexpected error: " + repr(stderr)) + @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_join_nondaemon_on_shutdown(self): # Issue 1722344 # Raising SystemExit skipped threading._shutdown @@ -386,6 +394,7 @@ finally: sys.setcheckinterval(old_interval) + @unittest.skipIf(is_jython, "Does not apply to Jython") def test_no_refcycle_through_target(self): class RunSelfFunction(object): def __init__(self, should_raise): @@ -668,6 +677,7 @@ class ThreadingExceptionTests(BaseTestCase): # A RuntimeError should be raised if Thread.start() is called # multiple times. + @unittest.skipIf(is_jython, "FIXME: not working properly on Jython") def test_start_thread_again(self): thread = threading.Thread() thread.start() @@ -678,10 +688,12 @@ current_thread = threading.current_thread() self.assertRaises(RuntimeError, current_thread.join); + @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_joining_inactive_thread(self): thread = threading.Thread() self.assertRaises(RuntimeError, thread.join) + @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_daemonize_active_thread(self): thread = threading.Thread() thread.start() @@ -708,6 +720,7 @@ class SemaphoreTests(lock_tests.SemaphoreTests): semtype = staticmethod(threading.Semaphore) + at unittest.skipIf(is_jython, "FIXME: investigate on Jython") class BoundedSemaphoreTests(lock_tests.BoundedSemaphoreTests): semtype = staticmethod(threading.BoundedSemaphore) -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 5 00:13:27 2013 From: jython-checkins at python.org (alan.kennedy) Date: Tue, 5 Feb 2013 00:13:27 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Tidying_up_java_exception_h?= =?utf-8?q?andling_code_with_a_decorator?= Message-ID: <3Z0PqC4zBRzSm4@mail.python.org> http://hg.python.org/jython/rev/f71726421d84 changeset: 6991:f71726421d84 user: Alan Kennedy date: Mon Feb 04 23:12:01 2013 +0000 summary: Tidying up java exception handling code with a decorator files: Lib/socket.py | 527 +++++++++++++++++-------------------- 1 files changed, 243 insertions(+), 284 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -159,6 +159,30 @@ setattr(py_exception, 'java_exception', java_exception) return _add_exception_attrs(py_exception) +from functools import wraps + +# Used to map java exceptions to the equivalent python exception +def raises_java_exception(method_or_function): + @wraps(method_or_function) + def map_exception(*args, **kwargs): + try: + return method_or_function(*args, **kwargs) + except java.lang.Exception, jlx: + raise _map_exception(jlx) + return map_exception + +# Used for SO_ERROR support. +def raises_error(method): + @wraps(method) + def set_last_error(obj, *args, **kwargs): + try: + setattr(obj, '_last_error', 0) + return method(obj, *args, **kwargs) + except error, e: + setattr(obj, '_last_error', e[0]) + raise + return set_last_error + _feature_support_map = { 'ipv6': True, 'idna': False, @@ -688,17 +712,13 @@ return a return name + at raises_java_exception def gethostname(): - try: - return asPyString(java.net.InetAddress.getLocalHost().getHostName()) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + return asPyString(java.net.InetAddress.getLocalHost().getHostName()) + at raises_java_exception def gethostbyname(name): - try: - return asPyString(java.net.InetAddress.getByName(name).getHostAddress()) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + return asPyString(java.net.InetAddress.getByName(name).getHostAddress()) # # Skeleton implementation of gethostbyname_ex @@ -935,54 +955,52 @@ int_port = int(port) return int_port % 65536 + at raises_java_exception def getaddrinfo(host, port, family=AF_UNSPEC, socktype=0, proto=0, flags=0): - try: - if _ipv4_addresses_only: - family = AF_INET - if not family in [AF_INET, AF_INET6, AF_UNSPEC]: - raise gaierror(errno.EIO, 'ai_family not supported') - host = _getaddrinfo_get_host(host, family, flags) - port = _getaddrinfo_get_port(port, flags) - if socktype not in [0, SOCK_DGRAM, SOCK_STREAM]: - raise error(errno.ESOCKTNOSUPPORT, "Socket type %s is not supported" % _constant_to_name(socktype, ['SOCK_'])) - filter_fns = [] - filter_fns.append({ - AF_INET: lambda x: isinstance(x, java.net.Inet4Address), - AF_INET6: lambda x: isinstance(x, java.net.Inet6Address), - AF_UNSPEC: lambda x: isinstance(x, java.net.InetAddress), - }[family]) - if host in [None, ""]: - if flags & AI_PASSIVE: - hosts = {AF_INET: [INADDR_ANY], AF_INET6: [IN6ADDR_ANY_INIT], AF_UNSPEC: [INADDR_ANY, IN6ADDR_ANY_INIT]}[family] - else: - hosts = ["localhost"] + if _ipv4_addresses_only: + family = AF_INET + if not family in [AF_INET, AF_INET6, AF_UNSPEC]: + raise gaierror(errno.EIO, 'ai_family not supported') + host = _getaddrinfo_get_host(host, family, flags) + port = _getaddrinfo_get_port(port, flags) + if socktype not in [0, SOCK_DGRAM, SOCK_STREAM]: + raise error(errno.ESOCKTNOSUPPORT, "Socket type %s is not supported" % _constant_to_name(socktype, ['SOCK_'])) + filter_fns = [] + filter_fns.append({ + AF_INET: lambda x: isinstance(x, java.net.Inet4Address), + AF_INET6: lambda x: isinstance(x, java.net.Inet6Address), + AF_UNSPEC: lambda x: isinstance(x, java.net.InetAddress), + }[family]) + if host in [None, ""]: + if flags & AI_PASSIVE: + hosts = {AF_INET: [INADDR_ANY], AF_INET6: [IN6ADDR_ANY_INIT], AF_UNSPEC: [INADDR_ANY, IN6ADDR_ANY_INIT]}[family] else: - hosts = [host] - results = [] - for h in hosts: - for a in java.net.InetAddress.getAllByName(h): - if len([f for f in filter_fns if f(a)]): - family = {java.net.Inet4Address: AF_INET, java.net.Inet6Address: AF_INET6}[a.getClass()] - if flags & AI_CANONNAME: - canonname = asPyString(a.getCanonicalHostName()) - else: - canonname = "" - sockaddr = asPyString(a.getHostAddress()) - # TODO: Include flowinfo and scopeid in a 4-tuple for IPv6 addresses - sock_tuple = {AF_INET : _ipv4_address_t, AF_INET6 : _ipv6_address_t}[family](sockaddr, port, a) - if socktype == 0: - socktypes = [SOCK_DGRAM, SOCK_STREAM] - else: - socktypes = [socktype] - for result_socktype in socktypes: - result_proto = {SOCK_DGRAM: IPPROTO_UDP, SOCK_STREAM: IPPROTO_TCP}[result_socktype] - if proto in [0, result_proto]: - # The returned socket will only support the result_proto - # If this does not match the requested proto, don't return it - results.append((family, result_socktype, result_proto, canonname, sock_tuple)) - return results - except java.lang.Exception, jlx: - raise _map_exception(jlx) + hosts = ["localhost"] + else: + hosts = [host] + results = [] + for h in hosts: + for a in java.net.InetAddress.getAllByName(h): + if len([f for f in filter_fns if f(a)]): + family = {java.net.Inet4Address: AF_INET, java.net.Inet6Address: AF_INET6}[a.getClass()] + if flags & AI_CANONNAME: + canonname = asPyString(a.getCanonicalHostName()) + else: + canonname = "" + sockaddr = asPyString(a.getHostAddress()) + # TODO: Include flowinfo and scopeid in a 4-tuple for IPv6 addresses + sock_tuple = {AF_INET : _ipv4_address_t, AF_INET6 : _ipv6_address_t}[family](sockaddr, port, a) + if socktype == 0: + socktypes = [SOCK_DGRAM, SOCK_STREAM] + else: + socktypes = [socktype] + for result_socktype in socktypes: + result_proto = {SOCK_DGRAM: IPPROTO_UDP, SOCK_STREAM: IPPROTO_TCP}[result_socktype] + if proto in [0, result_proto]: + # The returned socket will only support the result_proto + # If this does not match the requested proto, don't return it + results.append((family, result_socktype, result_proto, canonname, sock_tuple)) + return results def _getnameinfo_get_host(address, flags): if not isinstance(address, basestring): @@ -1047,41 +1065,35 @@ def ntohl(x): return x def inet_pton(family, ip_string): - try: - if family == AF_INET: - if not is_ipv4_address(ip_string): - raise error("illegal IP address string passed to inet_pton") - elif family == AF_INET6: - if not is_ipv6_address(ip_string): - raise error("illegal IP address string passed to inet_pton") + if family == AF_INET: + if not is_ipv4_address(ip_string): + raise error("illegal IP address string passed to inet_pton") + elif family == AF_INET6: + if not is_ipv6_address(ip_string): + raise error("illegal IP address string passed to inet_pton") + else: + raise error(errno.EAFNOSUPPORT, "Address family not supported by protocol") + ia = java.net.InetAddress.getByName(ip_string) + bytes = [] + for byte in ia.getAddress(): + if byte < 0: + bytes.append(byte+256) else: - raise error(errno.EAFNOSUPPORT, "Address family not supported by protocol") - ia = java.net.InetAddress.getByName(ip_string) - bytes = [] - for byte in ia.getAddress(): - if byte < 0: - bytes.append(byte+256) - else: - bytes.append(byte) - return "".join([chr(byte) for byte in bytes]) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + bytes.append(byte) + return "".join([chr(byte) for byte in bytes]) def inet_ntop(family, packed_ip): - try: - jByteArray = jarray.array(packed_ip, 'b') - if family == AF_INET: - if len(jByteArray) != 4: - raise ValueError("invalid length of packed IP address string") - elif family == AF_INET6: - if len(jByteArray) != 16: - raise ValueError("invalid length of packed IP address string") - else: - raise ValueError("unknown address family %s" % family) - ia = java.net.InetAddress.getByAddress(jByteArray) - return ia.getHostAddress() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + jByteArray = jarray.array(packed_ip, 'b') + if family == AF_INET: + if len(jByteArray) != 4: + raise ValueError("invalid length of packed IP address string") + elif family == AF_INET6: + if len(jByteArray) != 16: + raise ValueError("invalid length of packed IP address string") + else: + raise ValueError("unknown address family %s" % family) + ia = java.net.InetAddress.getByAddress(jByteArray) + return ia.getHostAddress() def inet_aton(ip_string): return inet_pton(AF_INET, ip_string) @@ -1089,18 +1101,6 @@ def inet_ntoa(packed_ip): return inet_ntop(AF_INET, packed_ip) -from functools import wraps -def raises_error(method): - @wraps(method) - def set_last_error(obj, *args, **kwargs): - try: - setattr(obj, '_last_error', 0) - return method(obj, *args, **kwargs) - except error, e: - setattr(obj, '_last_error', e[0]) - raise - return set_last_error - class _nonblocking_api_mixin: mode = MODE_BLOCKING @@ -1141,15 +1141,14 @@ return self.mode == MODE_BLOCKING @raises_error + @raises_java_exception def setsockopt(self, level, optname, value): - try: - if self.sock_impl: - self.sock_impl.setsockopt(level, optname, value) - else: - self.pending_options[ (level, optname) ] = value - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl: + self.sock_impl.setsockopt(level, optname, value) + else: + self.pending_options[ (level, optname) ] = value + @raises_java_exception def getsockopt(self, level, optname): # Handle "pseudo" options first if level == SOL_SOCKET and optname == SO_TYPE: @@ -1159,54 +1158,43 @@ self._last_error = 0 return return_value # Now handle "real" options - try: - if self.sock_impl: - return self.sock_impl.getsockopt(level, optname) - else: - return self.pending_options.get( (level, optname), None) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl: + return self.sock_impl.getsockopt(level, optname) + else: + return self.pending_options.get( (level, optname), None) @raises_error + @raises_java_exception def shutdown(self, how): assert how in (SHUT_RD, SHUT_WR, SHUT_RDWR) if not self.sock_impl: raise error(errno.ENOTCONN, "Transport endpoint is not connected") - try: - self.sock_impl.shutdown(how) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + self.sock_impl.shutdown(how) @raises_error + @raises_java_exception def close(self): - try: - if self.sock_impl: - self.sock_impl.close() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl: + self.sock_impl.close() @raises_error + @raises_java_exception def getsockname(self): - try: - if self.sock_impl is None: - # If the user has already bound an address, return that - if self.local_addr: - return self.local_addr - # The user has not bound, connected or listened - # This is what cpython raises in this scenario - raise error(errno.EINVAL, "Invalid argument") - return self.sock_impl.getsockname() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl is None: + # If the user has already bound an address, return that + if self.local_addr: + return self.local_addr + # The user has not bound, connected or listened + # This is what cpython raises in this scenario + raise error(errno.EINVAL, "Invalid argument") + return self.sock_impl.getsockname() @raises_error + @raises_java_exception def getpeername(self): - try: - if self.sock_impl is None: - raise error(errno.ENOTCONN, "Socket is not connected") - return self.sock_impl.getpeername() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl is None: + raise error(errno.ENOTCONN, "Socket is not connected") + return self.sock_impl.getpeername() def _config(self): assert self.mode in _permitted_modes @@ -1255,47 +1243,41 @@ self.local_addr = addr @raises_error + @raises_java_exception def listen(self, backlog): "This signifies a server socket" - try: - assert not self.sock_impl - self.server = 1 - self.sock_impl = _server_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), - backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) - self._config() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.sock_impl + self.server = 1 + self.sock_impl = _server_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), + backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) + self._config() @raises_error + @raises_java_exception def accept(self): "This signifies a server socket" - try: - if not self.sock_impl: - self.listen() - assert self.server - new_sock = self.sock_impl.accept() - if not new_sock: - raise would_block_error() - cliconn = _tcpsocket() - cliconn.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ] = new_sock.jsocket.getReuseAddress() - cliconn.sock_impl = new_sock - cliconn._setup() - return cliconn, new_sock.getpeername() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: + self.listen() + assert self.server + new_sock = self.sock_impl.accept() + if not new_sock: + raise would_block_error() + cliconn = _tcpsocket() + cliconn.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ] = new_sock.jsocket.getReuseAddress() + cliconn.sock_impl = new_sock + cliconn._setup() + return cliconn, new_sock.getpeername() @raises_error + @raises_java_exception def _do_connect(self, addr): - try: - assert not self.sock_impl - self.sock_impl = _client_socket_impl() - if self.local_addr: # Has the socket been bound to a local address? - self.sock_impl.bind(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, 0), - self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) - self._config() # Configure timeouts, etc, now that the socket exists - self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.sock_impl + self.sock_impl = _client_socket_impl() + if self.local_addr: # Has the socket been bound to a local address? + self.sock_impl.bind(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, 0), + self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) + self._config() # Configure timeouts, etc, now that the socket exists + self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) def connect(self, addr): "This signifies a client socket" @@ -1319,54 +1301,48 @@ self.ostream = self.sock_impl.jsocket.getOutputStream() @raises_error + @raises_java_exception def recv(self, n): - try: - if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') - if self.sock_impl.jchannel.isConnectionPending(): - self.sock_impl.jchannel.finishConnect() - data = jarray.zeros(n, 'b') - m = self.sock_impl.read(data) - if m == -1:#indicates EOF has been reached, so we just return the empty string - return "" - elif m <= 0: - if self.mode == MODE_NONBLOCKING: - raise would_block_error() - return "" - if m < n: - data = data[:m] - return data.tostring() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') + if self.sock_impl.jchannel.isConnectionPending(): + self.sock_impl.jchannel.finishConnect() + data = jarray.zeros(n, 'b') + m = self.sock_impl.read(data) + if m == -1:#indicates EOF has been reached, so we just return the empty string + return "" + elif m <= 0: + if self.mode == MODE_NONBLOCKING: + raise would_block_error() + return "" + if m < n: + data = data[:m] + return data.tostring() def recvfrom(self, n): return self.recv(n), None @raises_error + @raises_java_exception def send(self, s): - try: - if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') - if self.sock_impl.jchannel.isConnectionPending(): - self.sock_impl.jchannel.finishConnect() - numwritten = self.sock_impl.write(s) - if numwritten == 0 and self.mode == MODE_NONBLOCKING: - raise would_block_error() - return numwritten - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') + if self.sock_impl.jchannel.isConnectionPending(): + self.sock_impl.jchannel.finishConnect() + numwritten = self.sock_impl.write(s) + if numwritten == 0 and self.mode == MODE_NONBLOCKING: + raise would_block_error() + return numwritten sendall = send @raises_error + @raises_java_exception def close(self): - try: - if self.istream: - self.istream.close() - if self.ostream: - self.ostream.close() - if self.sock_impl: - self.sock_impl.close() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.istream: + self.istream.close() + if self.ostream: + self.ostream.close() + if self.sock_impl: + self.sock_impl.close() class _udpsocket(_nonblocking_api_mixin): @@ -1380,30 +1356,26 @@ _nonblocking_api_mixin.__init__(self) @raises_error + @raises_java_exception def bind(self, addr): - try: - assert not self.sock_impl - assert not self.local_addr - # Do the address format check - _get_jsockaddr(addr, self.family, self.type, self.proto, AI_PASSIVE) - self.local_addr = addr - self.sock_impl = _datagram_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), - self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) - self._config() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.sock_impl + assert not self.local_addr + # Do the address format check + _get_jsockaddr(addr, self.family, self.type, self.proto, AI_PASSIVE) + self.local_addr = addr + self.sock_impl = _datagram_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), + self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) + self._config() @raises_error + @raises_java_exception def _do_connect(self, addr): - try: - assert not self.connected, "Datagram Socket is already connected" - if not self.sock_impl: - self.sock_impl = _datagram_socket_impl() - self._config() - self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) - self.connected = True - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.connected, "Datagram Socket is already connected" + if not self.sock_impl: + self.sock_impl = _datagram_socket_impl() + self._config() + self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) + self.connected = True def connect(self, addr): self._do_connect(addr) @@ -1414,20 +1386,18 @@ return 0 @raises_error + @raises_java_exception def sendto(self, data, p1, p2=None): - try: - if not p2: - flags, addr = 0, p1 - else: - flags, addr = 0, p2 - if not self.sock_impl: - self.sock_impl = _datagram_socket_impl() - self._config() - byte_array = java.lang.String(data).getBytes('iso-8859-1') - result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, self.family, self.type, self.proto, 0), flags) - return result - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not p2: + flags, addr = 0, p1 + else: + flags, addr = 0, p2 + if not self.sock_impl: + self.sock_impl = _datagram_socket_impl() + self._config() + byte_array = java.lang.String(data).getBytes('iso-8859-1') + result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, self.family, self.type, self.proto, 0), flags) + return result def send(self, data, flags=None): if not self.connected: raise error(errno.ENOTCONN, "Socket is not connected") @@ -1435,6 +1405,7 @@ return self.sock_impl.send(byte_array, flags) @raises_error + @raises_java_exception def recvfrom(self, num_bytes, flags=None): """ There is some disagreement as to what the behaviour should be if @@ -1443,26 +1414,22 @@ http://bugs.jython.org/issue1005 http://bugs.sun.com/view_bug.do?bug_id=6621689 """ - try: - # This is the old 2.1 behaviour - #assert self.sock_impl - # This is amak's preferred interpretation - #raise error(errno.ENOTCONN, "Recvfrom on unbound udp socket meaningless operation") - # And this is the option for cpython compatibility - if not self.sock_impl: - self.sock_impl = _datagram_socket_impl() - self._config() - return self.sock_impl.recvfrom(num_bytes, flags) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + # This is the old 2.1 behaviour + #assert self.sock_impl + # This is amak's preferred interpretation + #raise error(errno.ENOTCONN, "Recvfrom on unbound udp socket meaningless operation") + # And this is the option for cpython compatibility + if not self.sock_impl: + self.sock_impl = _datagram_socket_impl() + self._config() + return self.sock_impl.recvfrom(num_bytes, flags) @raises_error + @raises_java_exception def recv(self, num_bytes, flags=None): - if not self.sock_impl: raise error(errno.ENOTCONN, "Socket is not connected") - try: - return self.sock_impl.recv(num_bytes, flags) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: + raise error(errno.ENOTCONN, "Socket is not connected") + return self.sock_impl.recv(num_bytes, flags) def __del__(self): self.close() @@ -1858,15 +1825,13 @@ class ssl: + @raises_java_exception def __init__(self, jython_socket_wrapper, keyfile=None, certfile=None): - try: - self.jython_socket_wrapper = jython_socket_wrapper - jython_socket = self.jython_socket_wrapper._sock - self.java_ssl_socket = self._make_ssl_socket(jython_socket) - self._in_buf = java.io.BufferedInputStream(self.java_ssl_socket.getInputStream()) - self._out_buf = java.io.BufferedOutputStream(self.java_ssl_socket.getOutputStream()) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + self.jython_socket_wrapper = jython_socket_wrapper + jython_socket = self.jython_socket_wrapper._sock + self.java_ssl_socket = self._make_ssl_socket(jython_socket) + self._in_buf = java.io.BufferedInputStream(self.java_ssl_socket.getInputStream()) + self._out_buf = java.io.BufferedOutputStream(self.java_ssl_socket.getOutputStream()) def _make_ssl_socket(self, jython_socket, auto_close=0): java_net_socket = jython_socket._get_jsocket() @@ -1884,31 +1849,25 @@ return getattr(self.jython_socket_wrapper, attr_name) raise AttributeError(attr_name) + @raises_java_exception def read(self, n=4096): - try: - data = jarray.zeros(n, 'b') - m = self._in_buf.read(data, 0, n) - if m <= 0: - return "" - if m < n: - data = data[:m] - return data.tostring() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + data = jarray.zeros(n, 'b') + m = self._in_buf.read(data, 0, n) + if m <= 0: + return "" + if m < n: + data = data[:m] + return data.tostring() + @raises_java_exception def write(self, s): - try: - self._out_buf.write(s) - self._out_buf.flush() - return len(s) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + self._out_buf.write(s) + self._out_buf.flush() + return len(s) + @raises_java_exception def _get_server_cert(self): - try: - return self.java_ssl_socket.getSession().getPeerCertificates()[0] - except java.lang.Exception, jlx: - raise _map_exception(jlx) + return self.java_ssl_socket.getSession().getPeerCertificates()[0] def server(self): cert = self._get_server_cert() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 5 00:29:14 2013 From: jython-checkins at python.org (alan.kennedy) Date: Tue, 5 Feb 2013 00:29:14 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Missed_two_functions_in_the?= =?utf-8?q?_last_checkin?= Message-ID: <3Z0Q9R00v6zQSc@mail.python.org> http://hg.python.org/jython/rev/941e8416cbc5 changeset: 6992:941e8416cbc5 user: Alan Kennedy date: Mon Feb 04 23:27:56 2013 +0000 summary: Missed two functions in the last checkin files: Lib/socket.py | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -1064,6 +1064,7 @@ def ntohs(x): return x def ntohl(x): return x + at raises_java_exception def inet_pton(family, ip_string): if family == AF_INET: if not is_ipv4_address(ip_string): @@ -1082,6 +1083,7 @@ bytes.append(byte) return "".join([chr(byte) for byte in bytes]) + at raises_java_exception def inet_ntop(family, packed_ip): jByteArray = jarray.array(packed_ip, 'b') if family == AF_INET: -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 5 01:13:18 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 5 Feb 2013 01:13:18 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231327_Add_ThreadContext_c?= =?utf-8?q?lass_to_finish_this_fix_=28oops=29=2E?= Message-ID: <3Z0R8G5f10zSj5@mail.python.org> http://hg.python.org/jython/rev/8c899b08a774 changeset: 6993:8c899b08a774 parent: 6990:d8f218899c2c user: Frank Wierzbicki date: Mon Feb 04 15:20:05 2013 -0800 summary: #1327 Add ThreadContext class to finish this fix (oops). files: src/org/python/core/ThreadContext.java | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-) diff --git a/src/org/python/core/ThreadContext.java b/src/org/python/core/ThreadContext.java new file mode 100644 --- /dev/null +++ b/src/org/python/core/ThreadContext.java @@ -0,0 +1,8 @@ +// Used to manage re-entrant context on the stack, as opposed to a thread-specific global state +package org.python.core; + +class ThreadContext { + + static ThreadLocal initializingProxy = new ThreadLocal(); + +} \ No newline at end of file -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 5 01:13:20 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 5 Feb 2013 01:13:20 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?b?KTogTWVyZ2Uu?= Message-ID: <3Z0R8J4hhTzSjH@mail.python.org> http://hg.python.org/jython/rev/846fb19054c4 changeset: 6994:846fb19054c4 parent: 6993:8c899b08a774 parent: 6992:941e8416cbc5 user: Frank Wierzbicki date: Mon Feb 04 16:13:09 2013 -0800 summary: Merge. files: Lib/socket.py | 529 +++++++++++++++++-------------------- 1 files changed, 245 insertions(+), 284 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -159,6 +159,30 @@ setattr(py_exception, 'java_exception', java_exception) return _add_exception_attrs(py_exception) +from functools import wraps + +# Used to map java exceptions to the equivalent python exception +def raises_java_exception(method_or_function): + @wraps(method_or_function) + def map_exception(*args, **kwargs): + try: + return method_or_function(*args, **kwargs) + except java.lang.Exception, jlx: + raise _map_exception(jlx) + return map_exception + +# Used for SO_ERROR support. +def raises_error(method): + @wraps(method) + def set_last_error(obj, *args, **kwargs): + try: + setattr(obj, '_last_error', 0) + return method(obj, *args, **kwargs) + except error, e: + setattr(obj, '_last_error', e[0]) + raise + return set_last_error + _feature_support_map = { 'ipv6': True, 'idna': False, @@ -688,17 +712,13 @@ return a return name + at raises_java_exception def gethostname(): - try: - return asPyString(java.net.InetAddress.getLocalHost().getHostName()) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + return asPyString(java.net.InetAddress.getLocalHost().getHostName()) + at raises_java_exception def gethostbyname(name): - try: - return asPyString(java.net.InetAddress.getByName(name).getHostAddress()) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + return asPyString(java.net.InetAddress.getByName(name).getHostAddress()) # # Skeleton implementation of gethostbyname_ex @@ -935,54 +955,52 @@ int_port = int(port) return int_port % 65536 + at raises_java_exception def getaddrinfo(host, port, family=AF_UNSPEC, socktype=0, proto=0, flags=0): - try: - if _ipv4_addresses_only: - family = AF_INET - if not family in [AF_INET, AF_INET6, AF_UNSPEC]: - raise gaierror(errno.EIO, 'ai_family not supported') - host = _getaddrinfo_get_host(host, family, flags) - port = _getaddrinfo_get_port(port, flags) - if socktype not in [0, SOCK_DGRAM, SOCK_STREAM]: - raise error(errno.ESOCKTNOSUPPORT, "Socket type %s is not supported" % _constant_to_name(socktype, ['SOCK_'])) - filter_fns = [] - filter_fns.append({ - AF_INET: lambda x: isinstance(x, java.net.Inet4Address), - AF_INET6: lambda x: isinstance(x, java.net.Inet6Address), - AF_UNSPEC: lambda x: isinstance(x, java.net.InetAddress), - }[family]) - if host in [None, ""]: - if flags & AI_PASSIVE: - hosts = {AF_INET: [INADDR_ANY], AF_INET6: [IN6ADDR_ANY_INIT], AF_UNSPEC: [INADDR_ANY, IN6ADDR_ANY_INIT]}[family] - else: - hosts = ["localhost"] + if _ipv4_addresses_only: + family = AF_INET + if not family in [AF_INET, AF_INET6, AF_UNSPEC]: + raise gaierror(errno.EIO, 'ai_family not supported') + host = _getaddrinfo_get_host(host, family, flags) + port = _getaddrinfo_get_port(port, flags) + if socktype not in [0, SOCK_DGRAM, SOCK_STREAM]: + raise error(errno.ESOCKTNOSUPPORT, "Socket type %s is not supported" % _constant_to_name(socktype, ['SOCK_'])) + filter_fns = [] + filter_fns.append({ + AF_INET: lambda x: isinstance(x, java.net.Inet4Address), + AF_INET6: lambda x: isinstance(x, java.net.Inet6Address), + AF_UNSPEC: lambda x: isinstance(x, java.net.InetAddress), + }[family]) + if host in [None, ""]: + if flags & AI_PASSIVE: + hosts = {AF_INET: [INADDR_ANY], AF_INET6: [IN6ADDR_ANY_INIT], AF_UNSPEC: [INADDR_ANY, IN6ADDR_ANY_INIT]}[family] else: - hosts = [host] - results = [] - for h in hosts: - for a in java.net.InetAddress.getAllByName(h): - if len([f for f in filter_fns if f(a)]): - family = {java.net.Inet4Address: AF_INET, java.net.Inet6Address: AF_INET6}[a.getClass()] - if flags & AI_CANONNAME: - canonname = asPyString(a.getCanonicalHostName()) - else: - canonname = "" - sockaddr = asPyString(a.getHostAddress()) - # TODO: Include flowinfo and scopeid in a 4-tuple for IPv6 addresses - sock_tuple = {AF_INET : _ipv4_address_t, AF_INET6 : _ipv6_address_t}[family](sockaddr, port, a) - if socktype == 0: - socktypes = [SOCK_DGRAM, SOCK_STREAM] - else: - socktypes = [socktype] - for result_socktype in socktypes: - result_proto = {SOCK_DGRAM: IPPROTO_UDP, SOCK_STREAM: IPPROTO_TCP}[result_socktype] - if proto in [0, result_proto]: - # The returned socket will only support the result_proto - # If this does not match the requested proto, don't return it - results.append((family, result_socktype, result_proto, canonname, sock_tuple)) - return results - except java.lang.Exception, jlx: - raise _map_exception(jlx) + hosts = ["localhost"] + else: + hosts = [host] + results = [] + for h in hosts: + for a in java.net.InetAddress.getAllByName(h): + if len([f for f in filter_fns if f(a)]): + family = {java.net.Inet4Address: AF_INET, java.net.Inet6Address: AF_INET6}[a.getClass()] + if flags & AI_CANONNAME: + canonname = asPyString(a.getCanonicalHostName()) + else: + canonname = "" + sockaddr = asPyString(a.getHostAddress()) + # TODO: Include flowinfo and scopeid in a 4-tuple for IPv6 addresses + sock_tuple = {AF_INET : _ipv4_address_t, AF_INET6 : _ipv6_address_t}[family](sockaddr, port, a) + if socktype == 0: + socktypes = [SOCK_DGRAM, SOCK_STREAM] + else: + socktypes = [socktype] + for result_socktype in socktypes: + result_proto = {SOCK_DGRAM: IPPROTO_UDP, SOCK_STREAM: IPPROTO_TCP}[result_socktype] + if proto in [0, result_proto]: + # The returned socket will only support the result_proto + # If this does not match the requested proto, don't return it + results.append((family, result_socktype, result_proto, canonname, sock_tuple)) + return results def _getnameinfo_get_host(address, flags): if not isinstance(address, basestring): @@ -1046,42 +1064,38 @@ def ntohs(x): return x def ntohl(x): return x + at raises_java_exception def inet_pton(family, ip_string): - try: - if family == AF_INET: - if not is_ipv4_address(ip_string): - raise error("illegal IP address string passed to inet_pton") - elif family == AF_INET6: - if not is_ipv6_address(ip_string): - raise error("illegal IP address string passed to inet_pton") + if family == AF_INET: + if not is_ipv4_address(ip_string): + raise error("illegal IP address string passed to inet_pton") + elif family == AF_INET6: + if not is_ipv6_address(ip_string): + raise error("illegal IP address string passed to inet_pton") + else: + raise error(errno.EAFNOSUPPORT, "Address family not supported by protocol") + ia = java.net.InetAddress.getByName(ip_string) + bytes = [] + for byte in ia.getAddress(): + if byte < 0: + bytes.append(byte+256) else: - raise error(errno.EAFNOSUPPORT, "Address family not supported by protocol") - ia = java.net.InetAddress.getByName(ip_string) - bytes = [] - for byte in ia.getAddress(): - if byte < 0: - bytes.append(byte+256) - else: - bytes.append(byte) - return "".join([chr(byte) for byte in bytes]) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + bytes.append(byte) + return "".join([chr(byte) for byte in bytes]) + at raises_java_exception def inet_ntop(family, packed_ip): - try: - jByteArray = jarray.array(packed_ip, 'b') - if family == AF_INET: - if len(jByteArray) != 4: - raise ValueError("invalid length of packed IP address string") - elif family == AF_INET6: - if len(jByteArray) != 16: - raise ValueError("invalid length of packed IP address string") - else: - raise ValueError("unknown address family %s" % family) - ia = java.net.InetAddress.getByAddress(jByteArray) - return ia.getHostAddress() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + jByteArray = jarray.array(packed_ip, 'b') + if family == AF_INET: + if len(jByteArray) != 4: + raise ValueError("invalid length of packed IP address string") + elif family == AF_INET6: + if len(jByteArray) != 16: + raise ValueError("invalid length of packed IP address string") + else: + raise ValueError("unknown address family %s" % family) + ia = java.net.InetAddress.getByAddress(jByteArray) + return ia.getHostAddress() def inet_aton(ip_string): return inet_pton(AF_INET, ip_string) @@ -1089,18 +1103,6 @@ def inet_ntoa(packed_ip): return inet_ntop(AF_INET, packed_ip) -from functools import wraps -def raises_error(method): - @wraps(method) - def set_last_error(obj, *args, **kwargs): - try: - setattr(obj, '_last_error', 0) - return method(obj, *args, **kwargs) - except error, e: - setattr(obj, '_last_error', e[0]) - raise - return set_last_error - class _nonblocking_api_mixin: mode = MODE_BLOCKING @@ -1141,15 +1143,14 @@ return self.mode == MODE_BLOCKING @raises_error + @raises_java_exception def setsockopt(self, level, optname, value): - try: - if self.sock_impl: - self.sock_impl.setsockopt(level, optname, value) - else: - self.pending_options[ (level, optname) ] = value - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl: + self.sock_impl.setsockopt(level, optname, value) + else: + self.pending_options[ (level, optname) ] = value + @raises_java_exception def getsockopt(self, level, optname): # Handle "pseudo" options first if level == SOL_SOCKET and optname == SO_TYPE: @@ -1159,54 +1160,43 @@ self._last_error = 0 return return_value # Now handle "real" options - try: - if self.sock_impl: - return self.sock_impl.getsockopt(level, optname) - else: - return self.pending_options.get( (level, optname), None) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl: + return self.sock_impl.getsockopt(level, optname) + else: + return self.pending_options.get( (level, optname), None) @raises_error + @raises_java_exception def shutdown(self, how): assert how in (SHUT_RD, SHUT_WR, SHUT_RDWR) if not self.sock_impl: raise error(errno.ENOTCONN, "Transport endpoint is not connected") - try: - self.sock_impl.shutdown(how) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + self.sock_impl.shutdown(how) @raises_error + @raises_java_exception def close(self): - try: - if self.sock_impl: - self.sock_impl.close() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl: + self.sock_impl.close() @raises_error + @raises_java_exception def getsockname(self): - try: - if self.sock_impl is None: - # If the user has already bound an address, return that - if self.local_addr: - return self.local_addr - # The user has not bound, connected or listened - # This is what cpython raises in this scenario - raise error(errno.EINVAL, "Invalid argument") - return self.sock_impl.getsockname() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl is None: + # If the user has already bound an address, return that + if self.local_addr: + return self.local_addr + # The user has not bound, connected or listened + # This is what cpython raises in this scenario + raise error(errno.EINVAL, "Invalid argument") + return self.sock_impl.getsockname() @raises_error + @raises_java_exception def getpeername(self): - try: - if self.sock_impl is None: - raise error(errno.ENOTCONN, "Socket is not connected") - return self.sock_impl.getpeername() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.sock_impl is None: + raise error(errno.ENOTCONN, "Socket is not connected") + return self.sock_impl.getpeername() def _config(self): assert self.mode in _permitted_modes @@ -1255,47 +1245,41 @@ self.local_addr = addr @raises_error + @raises_java_exception def listen(self, backlog): "This signifies a server socket" - try: - assert not self.sock_impl - self.server = 1 - self.sock_impl = _server_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), - backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) - self._config() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.sock_impl + self.server = 1 + self.sock_impl = _server_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), + backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) + self._config() @raises_error + @raises_java_exception def accept(self): "This signifies a server socket" - try: - if not self.sock_impl: - self.listen() - assert self.server - new_sock = self.sock_impl.accept() - if not new_sock: - raise would_block_error() - cliconn = _tcpsocket() - cliconn.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ] = new_sock.jsocket.getReuseAddress() - cliconn.sock_impl = new_sock - cliconn._setup() - return cliconn, new_sock.getpeername() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: + self.listen() + assert self.server + new_sock = self.sock_impl.accept() + if not new_sock: + raise would_block_error() + cliconn = _tcpsocket() + cliconn.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ] = new_sock.jsocket.getReuseAddress() + cliconn.sock_impl = new_sock + cliconn._setup() + return cliconn, new_sock.getpeername() @raises_error + @raises_java_exception def _do_connect(self, addr): - try: - assert not self.sock_impl - self.sock_impl = _client_socket_impl() - if self.local_addr: # Has the socket been bound to a local address? - self.sock_impl.bind(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, 0), - self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) - self._config() # Configure timeouts, etc, now that the socket exists - self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.sock_impl + self.sock_impl = _client_socket_impl() + if self.local_addr: # Has the socket been bound to a local address? + self.sock_impl.bind(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, 0), + self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) + self._config() # Configure timeouts, etc, now that the socket exists + self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) def connect(self, addr): "This signifies a client socket" @@ -1319,54 +1303,48 @@ self.ostream = self.sock_impl.jsocket.getOutputStream() @raises_error + @raises_java_exception def recv(self, n): - try: - if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') - if self.sock_impl.jchannel.isConnectionPending(): - self.sock_impl.jchannel.finishConnect() - data = jarray.zeros(n, 'b') - m = self.sock_impl.read(data) - if m == -1:#indicates EOF has been reached, so we just return the empty string - return "" - elif m <= 0: - if self.mode == MODE_NONBLOCKING: - raise would_block_error() - return "" - if m < n: - data = data[:m] - return data.tostring() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') + if self.sock_impl.jchannel.isConnectionPending(): + self.sock_impl.jchannel.finishConnect() + data = jarray.zeros(n, 'b') + m = self.sock_impl.read(data) + if m == -1:#indicates EOF has been reached, so we just return the empty string + return "" + elif m <= 0: + if self.mode == MODE_NONBLOCKING: + raise would_block_error() + return "" + if m < n: + data = data[:m] + return data.tostring() def recvfrom(self, n): return self.recv(n), None @raises_error + @raises_java_exception def send(self, s): - try: - if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') - if self.sock_impl.jchannel.isConnectionPending(): - self.sock_impl.jchannel.finishConnect() - numwritten = self.sock_impl.write(s) - if numwritten == 0 and self.mode == MODE_NONBLOCKING: - raise would_block_error() - return numwritten - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') + if self.sock_impl.jchannel.isConnectionPending(): + self.sock_impl.jchannel.finishConnect() + numwritten = self.sock_impl.write(s) + if numwritten == 0 and self.mode == MODE_NONBLOCKING: + raise would_block_error() + return numwritten sendall = send @raises_error + @raises_java_exception def close(self): - try: - if self.istream: - self.istream.close() - if self.ostream: - self.ostream.close() - if self.sock_impl: - self.sock_impl.close() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if self.istream: + self.istream.close() + if self.ostream: + self.ostream.close() + if self.sock_impl: + self.sock_impl.close() class _udpsocket(_nonblocking_api_mixin): @@ -1380,30 +1358,26 @@ _nonblocking_api_mixin.__init__(self) @raises_error + @raises_java_exception def bind(self, addr): - try: - assert not self.sock_impl - assert not self.local_addr - # Do the address format check - _get_jsockaddr(addr, self.family, self.type, self.proto, AI_PASSIVE) - self.local_addr = addr - self.sock_impl = _datagram_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), - self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) - self._config() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.sock_impl + assert not self.local_addr + # Do the address format check + _get_jsockaddr(addr, self.family, self.type, self.proto, AI_PASSIVE) + self.local_addr = addr + self.sock_impl = _datagram_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), + self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) + self._config() @raises_error + @raises_java_exception def _do_connect(self, addr): - try: - assert not self.connected, "Datagram Socket is already connected" - if not self.sock_impl: - self.sock_impl = _datagram_socket_impl() - self._config() - self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) - self.connected = True - except java.lang.Exception, jlx: - raise _map_exception(jlx) + assert not self.connected, "Datagram Socket is already connected" + if not self.sock_impl: + self.sock_impl = _datagram_socket_impl() + self._config() + self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) + self.connected = True def connect(self, addr): self._do_connect(addr) @@ -1414,20 +1388,18 @@ return 0 @raises_error + @raises_java_exception def sendto(self, data, p1, p2=None): - try: - if not p2: - flags, addr = 0, p1 - else: - flags, addr = 0, p2 - if not self.sock_impl: - self.sock_impl = _datagram_socket_impl() - self._config() - byte_array = java.lang.String(data).getBytes('iso-8859-1') - result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, self.family, self.type, self.proto, 0), flags) - return result - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not p2: + flags, addr = 0, p1 + else: + flags, addr = 0, p2 + if not self.sock_impl: + self.sock_impl = _datagram_socket_impl() + self._config() + byte_array = java.lang.String(data).getBytes('iso-8859-1') + result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, self.family, self.type, self.proto, 0), flags) + return result def send(self, data, flags=None): if not self.connected: raise error(errno.ENOTCONN, "Socket is not connected") @@ -1435,6 +1407,7 @@ return self.sock_impl.send(byte_array, flags) @raises_error + @raises_java_exception def recvfrom(self, num_bytes, flags=None): """ There is some disagreement as to what the behaviour should be if @@ -1443,26 +1416,22 @@ http://bugs.jython.org/issue1005 http://bugs.sun.com/view_bug.do?bug_id=6621689 """ - try: - # This is the old 2.1 behaviour - #assert self.sock_impl - # This is amak's preferred interpretation - #raise error(errno.ENOTCONN, "Recvfrom on unbound udp socket meaningless operation") - # And this is the option for cpython compatibility - if not self.sock_impl: - self.sock_impl = _datagram_socket_impl() - self._config() - return self.sock_impl.recvfrom(num_bytes, flags) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + # This is the old 2.1 behaviour + #assert self.sock_impl + # This is amak's preferred interpretation + #raise error(errno.ENOTCONN, "Recvfrom on unbound udp socket meaningless operation") + # And this is the option for cpython compatibility + if not self.sock_impl: + self.sock_impl = _datagram_socket_impl() + self._config() + return self.sock_impl.recvfrom(num_bytes, flags) @raises_error + @raises_java_exception def recv(self, num_bytes, flags=None): - if not self.sock_impl: raise error(errno.ENOTCONN, "Socket is not connected") - try: - return self.sock_impl.recv(num_bytes, flags) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + if not self.sock_impl: + raise error(errno.ENOTCONN, "Socket is not connected") + return self.sock_impl.recv(num_bytes, flags) def __del__(self): self.close() @@ -1858,15 +1827,13 @@ class ssl: + @raises_java_exception def __init__(self, jython_socket_wrapper, keyfile=None, certfile=None): - try: - self.jython_socket_wrapper = jython_socket_wrapper - jython_socket = self.jython_socket_wrapper._sock - self.java_ssl_socket = self._make_ssl_socket(jython_socket) - self._in_buf = java.io.BufferedInputStream(self.java_ssl_socket.getInputStream()) - self._out_buf = java.io.BufferedOutputStream(self.java_ssl_socket.getOutputStream()) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + self.jython_socket_wrapper = jython_socket_wrapper + jython_socket = self.jython_socket_wrapper._sock + self.java_ssl_socket = self._make_ssl_socket(jython_socket) + self._in_buf = java.io.BufferedInputStream(self.java_ssl_socket.getInputStream()) + self._out_buf = java.io.BufferedOutputStream(self.java_ssl_socket.getOutputStream()) def _make_ssl_socket(self, jython_socket, auto_close=0): java_net_socket = jython_socket._get_jsocket() @@ -1884,31 +1851,25 @@ return getattr(self.jython_socket_wrapper, attr_name) raise AttributeError(attr_name) + @raises_java_exception def read(self, n=4096): - try: - data = jarray.zeros(n, 'b') - m = self._in_buf.read(data, 0, n) - if m <= 0: - return "" - if m < n: - data = data[:m] - return data.tostring() - except java.lang.Exception, jlx: - raise _map_exception(jlx) + data = jarray.zeros(n, 'b') + m = self._in_buf.read(data, 0, n) + if m <= 0: + return "" + if m < n: + data = data[:m] + return data.tostring() + @raises_java_exception def write(self, s): - try: - self._out_buf.write(s) - self._out_buf.flush() - return len(s) - except java.lang.Exception, jlx: - raise _map_exception(jlx) + self._out_buf.write(s) + self._out_buf.flush() + return len(s) + @raises_java_exception def _get_server_cert(self): - try: - return self.java_ssl_socket.getSession().getPeerCertificates()[0] - except java.lang.Exception, jlx: - raise _map_exception(jlx) + return self.java_ssl_socket.getSession().getPeerCertificates()[0] def server(self): cert = self._get_server_cert() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 5 01:50:36 2013 From: jython-checkins at python.org (alan.kennedy) Date: Tue, 5 Feb 2013 01:50:36 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Decorator_liberation?= Message-ID: <3Z0RzJ036nzSlS@mail.python.org> http://hg.python.org/jython/rev/a81d0ea19f7b changeset: 6995:a81d0ea19f7b user: Alan Kennedy date: Tue Feb 05 00:48:52 2013 +0000 summary: Decorator liberation files: Lib/socket.py | 19 +++++++++++++++---- 1 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -725,9 +725,11 @@ # Needed because urllib2 refers to it # + at raises_java_exception def gethostbyname_ex(name): return (name, [], gethostbyname(name)) + at raises_java_exception def gethostbyaddr(name): names, addrs = _gethostbyaddr(name) return (names[0], names, addrs) @@ -1029,6 +1031,7 @@ proto = "udp" return getservbyport(port, proto) + at raises_java_exception def getnameinfo(sock_addr, flags): if not isinstance(sock_addr, tuple) or len(sock_addr) < 2: raise TypeError("getnameinfo() argument 1 must be a tuple") @@ -1237,6 +1240,7 @@ return _nonblocking_api_mixin.getsockopt(self, level, optname) @raises_error + @raises_java_exception def bind(self, addr): assert not self.sock_impl assert not self.local_addr @@ -1270,8 +1274,6 @@ cliconn._setup() return cliconn, new_sock.getpeername() - @raises_error - @raises_java_exception def _do_connect(self, addr): assert not self.sock_impl self.sock_impl = _client_socket_impl() @@ -1281,11 +1283,15 @@ self._config() # Configure timeouts, etc, now that the socket exists self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) + @raises_error + @raises_java_exception def connect(self, addr): "This signifies a client socket" self._do_connect(addr) self._setup() + @raises_error + @raises_java_exception def connect_ex(self, addr): "This signifies a client socket" if not self.sock_impl: @@ -1320,6 +1326,8 @@ data = data[:m] return data.tostring() + @raises_error + @raises_java_exception def recvfrom(self, n): return self.recv(n), None @@ -1369,8 +1377,6 @@ self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) self._config() - @raises_error - @raises_java_exception def _do_connect(self, addr): assert not self.connected, "Datagram Socket is already connected" if not self.sock_impl: @@ -1379,9 +1385,13 @@ self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) self.connected = True + @raises_error + @raises_java_exception def connect(self, addr): self._do_connect(addr) + @raises_error + @raises_java_exception def connect_ex(self, addr): if not self.sock_impl: self._do_connect(addr) @@ -1401,6 +1411,7 @@ result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, self.family, self.type, self.proto, 0), flags) return result + @raises_java_exception def send(self, data, flags=None): if not self.connected: raise error(errno.ENOTCONN, "Socket is not connected") byte_array = java.lang.String(data).getBytes('iso-8859-1') -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 5 20:30:28 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 5 Feb 2013 20:30:28 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231968_Fixes_for_test=5Fcs?= =?utf-8?q?v=2Epy=2E_Thanks_Julian_Kennedy!?= Message-ID: <3Z0wqS1M66zSWg@mail.python.org> http://hg.python.org/jython/rev/faf635092bdb changeset: 6996:faf635092bdb parent: 6994:846fb19054c4 user: Frank Wierzbicki date: Tue Feb 05 11:29:27 2013 -0800 summary: #1968 Fixes for test_csv.py. Thanks Julian Kennedy! files: Lib/test/test_csv.py | 1072 ---------- NEWS | 1 + src/org/python/modules/_csv/PyWriter.java | 12 +- 3 files changed, 12 insertions(+), 1073 deletions(-) diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py deleted file mode 100644 --- a/Lib/test/test_csv.py +++ /dev/null @@ -1,1072 +0,0 @@ -# -*- coding: iso-8859-1 -*- -# Copyright (C) 2001,2002 Python Software Foundation -# csv package unit tests - -import sys -import os -import unittest -from StringIO import StringIO -import tempfile -import csv -import gc -import io -from test import test_support - -class Test_Csv(unittest.TestCase): - """ - Test the underlying C csv parser in ways that are not appropriate - from the high level interface. Further tests of this nature are done - in TestDialectRegistry. - """ - def _test_arg_valid(self, ctor, arg): - self.assertRaises(TypeError, ctor) - self.assertRaises(TypeError, ctor, None) - self.assertRaises(TypeError, ctor, arg, bad_attr = 0) - self.assertRaises(TypeError, ctor, arg, delimiter = 0) - self.assertRaises(TypeError, ctor, arg, delimiter = 'XX') - self.assertRaises(csv.Error, ctor, arg, 'foo') - self.assertRaises(TypeError, ctor, arg, delimiter=None) - self.assertRaises(TypeError, ctor, arg, delimiter=1) - self.assertRaises(TypeError, ctor, arg, quotechar=1) - self.assertRaises(TypeError, ctor, arg, lineterminator=None) - self.assertRaises(TypeError, ctor, arg, lineterminator=1) - self.assertRaises(TypeError, ctor, arg, quoting=None) - self.assertRaises(TypeError, ctor, arg, - quoting=csv.QUOTE_ALL, quotechar='') - self.assertRaises(TypeError, ctor, arg, - quoting=csv.QUOTE_ALL, quotechar=None) - - def test_reader_arg_valid(self): - self._test_arg_valid(csv.reader, []) - - def test_writer_arg_valid(self): - self._test_arg_valid(csv.writer, StringIO()) - - def _test_default_attrs(self, ctor, *args): - obj = ctor(*args) - # Check defaults - self.assertEqual(obj.dialect.delimiter, ',') - self.assertEqual(obj.dialect.doublequote, True) - self.assertEqual(obj.dialect.escapechar, None) - self.assertEqual(obj.dialect.lineterminator, "\r\n") - self.assertEqual(obj.dialect.quotechar, '"') - self.assertEqual(obj.dialect.quoting, csv.QUOTE_MINIMAL) - self.assertEqual(obj.dialect.skipinitialspace, False) - self.assertEqual(obj.dialect.strict, False) - # Try deleting or changing attributes (they are read-only) - self.assertRaises(TypeError, delattr, obj.dialect, 'delimiter') - self.assertRaises(TypeError, setattr, obj.dialect, 'delimiter', ':') - self.assertRaises(AttributeError, delattr, obj.dialect, 'quoting') - self.assertRaises(AttributeError, setattr, obj.dialect, - 'quoting', None) - - def test_reader_attrs(self): - self._test_default_attrs(csv.reader, []) - - def test_writer_attrs(self): - self._test_default_attrs(csv.writer, StringIO()) - - def _test_kw_attrs(self, ctor, *args): - # Now try with alternate options - kwargs = dict(delimiter=':', doublequote=False, escapechar='\\', - lineterminator='\r', quotechar='*', - quoting=csv.QUOTE_NONE, skipinitialspace=True, - strict=True) - obj = ctor(*args, **kwargs) - self.assertEqual(obj.dialect.delimiter, ':') - self.assertEqual(obj.dialect.doublequote, False) - self.assertEqual(obj.dialect.escapechar, '\\') - self.assertEqual(obj.dialect.lineterminator, "\r") - self.assertEqual(obj.dialect.quotechar, '*') - self.assertEqual(obj.dialect.quoting, csv.QUOTE_NONE) - self.assertEqual(obj.dialect.skipinitialspace, True) - self.assertEqual(obj.dialect.strict, True) - - def test_reader_kw_attrs(self): - self._test_kw_attrs(csv.reader, []) - - def test_writer_kw_attrs(self): - self._test_kw_attrs(csv.writer, StringIO()) - - def _test_dialect_attrs(self, ctor, *args): - # Now try with dialect-derived options - class dialect: - delimiter='-' - doublequote=False - escapechar='^' - lineterminator='$' - quotechar='#' - quoting=csv.QUOTE_ALL - skipinitialspace=True - strict=False - args = args + (dialect,) - obj = ctor(*args) - self.assertEqual(obj.dialect.delimiter, '-') - self.assertEqual(obj.dialect.doublequote, False) - self.assertEqual(obj.dialect.escapechar, '^') - self.assertEqual(obj.dialect.lineterminator, "$") - self.assertEqual(obj.dialect.quotechar, '#') - self.assertEqual(obj.dialect.quoting, csv.QUOTE_ALL) - self.assertEqual(obj.dialect.skipinitialspace, True) - self.assertEqual(obj.dialect.strict, False) - - def test_reader_dialect_attrs(self): - self._test_dialect_attrs(csv.reader, []) - - def test_writer_dialect_attrs(self): - self._test_dialect_attrs(csv.writer, StringIO()) - - - def _write_test(self, fields, expect, **kwargs): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, **kwargs) - writer.writerow(fields) - fileobj.seek(0) - self.assertEqual(fileobj.read(), - expect + writer.dialect.lineterminator) - finally: - fileobj.close() - os.unlink(name) - - def test_write_arg_valid(self): - self.assertRaises(csv.Error, self._write_test, None, '') - self._write_test((), '') - self._write_test([None], '""') - self.assertRaises(csv.Error, self._write_test, - [None], None, quoting = csv.QUOTE_NONE) - # Check that exceptions are passed up the chain - class BadList: - def __len__(self): - return 10; - def __getitem__(self, i): - if i > 2: - raise IOError - self.assertRaises(IOError, self._write_test, BadList(), '') - class BadItem: - def __str__(self): - raise IOError - self.assertRaises(IOError, self._write_test, [BadItem()], '') - - def test_write_bigfield(self): - # This exercises the buffer realloc functionality - bigstring = 'X' * 50000 - self._write_test([bigstring,bigstring], '%s,%s' % \ - (bigstring, bigstring)) - - def test_write_quoting(self): - self._write_test(['a',1,'p,q'], 'a,1,"p,q"') - self.assertRaises(csv.Error, - self._write_test, - ['a',1,'p,q'], 'a,1,p,q', - quoting = csv.QUOTE_NONE) - self._write_test(['a',1,'p,q'], 'a,1,"p,q"', - quoting = csv.QUOTE_MINIMAL) - self._write_test(['a',1,'p,q'], '"a",1,"p,q"', - quoting = csv.QUOTE_NONNUMERIC) - self._write_test(['a',1,'p,q'], '"a","1","p,q"', - quoting = csv.QUOTE_ALL) - self._write_test(['a\nb',1], '"a\nb","1"', - quoting = csv.QUOTE_ALL) - - def test_write_escape(self): - self._write_test(['a',1,'p,q'], 'a,1,"p,q"', - escapechar='\\') - self.assertRaises(csv.Error, - self._write_test, - ['a',1,'p,"q"'], 'a,1,"p,\\"q\\""', - escapechar=None, doublequote=False) - self._write_test(['a',1,'p,"q"'], 'a,1,"p,\\"q\\""', - escapechar='\\', doublequote = False) - self._write_test(['"'], '""""', - escapechar='\\', quoting = csv.QUOTE_MINIMAL) - self._write_test(['"'], '\\"', - escapechar='\\', quoting = csv.QUOTE_MINIMAL, - doublequote = False) - self._write_test(['"'], '\\"', - escapechar='\\', quoting = csv.QUOTE_NONE) - self._write_test(['a',1,'p,q'], 'a,1,p\\,q', - escapechar='\\', quoting = csv.QUOTE_NONE) - - def test_writerows(self): - class BrokenFile: - def write(self, buf): - raise IOError - writer = csv.writer(BrokenFile()) - self.assertRaises(IOError, writer.writerows, [['a']]) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj) - self.assertRaises(TypeError, writer.writerows, None) - writer.writerows([['a','b'],['c','d']]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "a,b\r\nc,d\r\n") - finally: - fileobj.close() - os.unlink(name) - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_write_float(self): - # Issue 13573: loss of precision because csv.writer - # uses str() for floats instead of repr() - orig_row = [1.234567890123, 1.0/7.0, 'abc'] - f = StringIO() - c = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC) - c.writerow(orig_row) - f.seek(0) - c = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC) - new_row = next(c) - self.assertEqual(orig_row, new_row) - - def _read_test(self, input, expect, **kwargs): - reader = csv.reader(input, **kwargs) - result = list(reader) - self.assertEqual(result, expect) - - def test_read_oddinputs(self): - self._read_test([], []) - self._read_test([''], [[]]) - self.assertRaises(csv.Error, self._read_test, - ['"ab"c'], None, strict = 1) - # cannot handle null bytes for the moment - self.assertRaises(csv.Error, self._read_test, - ['ab\0c'], None, strict = 1) - self._read_test(['"ab"c'], [['abc']], doublequote = 0) - - def test_read_eol(self): - self._read_test(['a,b'], [['a','b']]) - self._read_test(['a,b\n'], [['a','b']]) - self._read_test(['a,b\r\n'], [['a','b']]) - self._read_test(['a,b\r'], [['a','b']]) - self.assertRaises(csv.Error, self._read_test, ['a,b\rc,d'], []) - self.assertRaises(csv.Error, self._read_test, ['a,b\nc,d'], []) - self.assertRaises(csv.Error, self._read_test, ['a,b\r\nc,d'], []) - - def test_read_escape(self): - self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') - self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') - self._read_test(['a,"b\\,c"'], [['a', 'b,c']], escapechar='\\') - self._read_test(['a,"b,\\c"'], [['a', 'b,c']], escapechar='\\') - self._read_test(['a,"b,c\\""'], [['a', 'b,c"']], escapechar='\\') - self._read_test(['a,"b,c"\\'], [['a', 'b,c\\']], escapechar='\\') - - def test_read_quoting(self): - self._read_test(['1,",3,",5'], [['1', ',3,', '5']]) - self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], - quotechar=None, escapechar='\\') - self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], - quoting=csv.QUOTE_NONE, escapechar='\\') - # will this fail where locale uses comma for decimals? - self._read_test([',3,"5",7.3, 9'], [['', 3, '5', 7.3, 9]], - quoting=csv.QUOTE_NONNUMERIC) - self._read_test(['"a\nb", 7'], [['a\nb', ' 7']]) - self.assertRaises(ValueError, self._read_test, - ['abc,3'], [[]], - quoting=csv.QUOTE_NONNUMERIC) - - def test_read_bigfield(self): - # This exercises the buffer realloc functionality and field size - # limits. - limit = csv.field_size_limit() - try: - size = 50000 - bigstring = 'X' * size - bigline = '%s,%s' % (bigstring, bigstring) - self._read_test([bigline], [[bigstring, bigstring]]) - csv.field_size_limit(size) - self._read_test([bigline], [[bigstring, bigstring]]) - self.assertEqual(csv.field_size_limit(), size) - csv.field_size_limit(size-1) - self.assertRaises(csv.Error, self._read_test, [bigline], []) - self.assertRaises(TypeError, csv.field_size_limit, None) - self.assertRaises(TypeError, csv.field_size_limit, 1, None) - finally: - csv.field_size_limit(limit) - - def test_read_linenum(self): - for r in (csv.reader(['line,1', 'line,2', 'line,3']), - csv.DictReader(['line,1', 'line,2', 'line,3'], - fieldnames=['a', 'b', 'c'])): - self.assertEqual(r.line_num, 0) - r.next() - self.assertEqual(r.line_num, 1) - r.next() - self.assertEqual(r.line_num, 2) - r.next() - self.assertEqual(r.line_num, 3) - self.assertRaises(StopIteration, r.next) - self.assertEqual(r.line_num, 3) - - def test_roundtrip_quoteed_newlines(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj) - self.assertRaises(TypeError, writer.writerows, None) - rows = [['a\nb','b'],['c','x\r\nd']] - writer.writerows(rows) - fileobj.seek(0) - for i, row in enumerate(csv.reader(fileobj)): - self.assertEqual(row, rows[i]) - finally: - fileobj.close() - os.unlink(name) - -class TestDialectRegistry(unittest.TestCase): - def test_registry_badargs(self): - self.assertRaises(TypeError, csv.list_dialects, None) - self.assertRaises(TypeError, csv.get_dialect) - self.assertRaises(csv.Error, csv.get_dialect, None) - self.assertRaises(csv.Error, csv.get_dialect, "nonesuch") - self.assertRaises(TypeError, csv.unregister_dialect) - self.assertRaises(csv.Error, csv.unregister_dialect, None) - self.assertRaises(csv.Error, csv.unregister_dialect, "nonesuch") - self.assertRaises(TypeError, csv.register_dialect, None) - self.assertRaises(TypeError, csv.register_dialect, None, None) - self.assertRaises(TypeError, csv.register_dialect, "nonesuch", 0, 0) - self.assertRaises(TypeError, csv.register_dialect, "nonesuch", - badargument=None) - self.assertRaises(TypeError, csv.register_dialect, "nonesuch", - quoting=None) - self.assertRaises(TypeError, csv.register_dialect, []) - - def test_registry(self): - class myexceltsv(csv.excel): - delimiter = "\t" - name = "myexceltsv" - expected_dialects = csv.list_dialects() + [name] - expected_dialects.sort() - csv.register_dialect(name, myexceltsv) - self.addCleanup(csv.unregister_dialect, name) - self.assertEqual(csv.get_dialect(name).delimiter, '\t') - got_dialects = sorted(csv.list_dialects()) - self.assertEqual(expected_dialects, got_dialects) - - def test_register_kwargs(self): - name = 'fedcba' - csv.register_dialect(name, delimiter=';') - self.addCleanup(csv.unregister_dialect, name) - self.assertEqual(csv.get_dialect(name).delimiter, ';') - self.assertEqual([['X', 'Y', 'Z']], list(csv.reader(['X;Y;Z'], name))) - - def test_incomplete_dialect(self): - class myexceltsv(csv.Dialect): - delimiter = "\t" - self.assertRaises(csv.Error, myexceltsv) - - def test_space_dialect(self): - class space(csv.excel): - delimiter = " " - quoting = csv.QUOTE_NONE - escapechar = "\\" - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("abc def\nc1ccccc1 benzene\n") - fileobj.seek(0) - rdr = csv.reader(fileobj, dialect=space()) - self.assertEqual(rdr.next(), ["abc", "def"]) - self.assertEqual(rdr.next(), ["c1ccccc1", "benzene"]) - finally: - fileobj.close() - os.unlink(name) - - def test_dialect_apply(self): - class testA(csv.excel): - delimiter = "\t" - class testB(csv.excel): - delimiter = ":" - class testC(csv.excel): - delimiter = "|" - - csv.register_dialect('testC', testC) - try: - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj) - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1,2,3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, testA) - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1\t2\t3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect=testB()) - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1:2:3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect='testC') - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1|2|3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect=testA, delimiter=';') - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1;2;3\r\n") - finally: - fileobj.close() - os.unlink(name) - - finally: - csv.unregister_dialect('testC') - - def test_bad_dialect(self): - # Unknown parameter - self.assertRaises(TypeError, csv.reader, [], bad_attr = 0) - # Bad values - self.assertRaises(TypeError, csv.reader, [], delimiter = None) - self.assertRaises(TypeError, csv.reader, [], quoting = -1) - self.assertRaises(TypeError, csv.reader, [], quoting = 100) - -class TestCsvBase(unittest.TestCase): - def readerAssertEqual(self, input, expected_result): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write(input) - fileobj.seek(0) - reader = csv.reader(fileobj, dialect = self.dialect) - fields = list(reader) - self.assertEqual(fields, expected_result) - finally: - fileobj.close() - os.unlink(name) - - def writerAssertEqual(self, input, expected_result): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect = self.dialect) - writer.writerows(input) - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected_result) - finally: - fileobj.close() - os.unlink(name) - -class TestDialectExcel(TestCsvBase): - dialect = 'excel' - - def test_single(self): - self.readerAssertEqual('abc', [['abc']]) - - def test_simple(self): - self.readerAssertEqual('1,2,3,4,5', [['1','2','3','4','5']]) - - def test_blankline(self): - self.readerAssertEqual('', []) - - def test_empty_fields(self): - self.readerAssertEqual(',', [['', '']]) - - def test_singlequoted(self): - self.readerAssertEqual('""', [['']]) - - def test_singlequoted_left_empty(self): - self.readerAssertEqual('"",', [['','']]) - - def test_singlequoted_right_empty(self): - self.readerAssertEqual(',""', [['','']]) - - def test_single_quoted_quote(self): - self.readerAssertEqual('""""', [['"']]) - - def test_quoted_quotes(self): - self.readerAssertEqual('""""""', [['""']]) - - def test_inline_quote(self): - self.readerAssertEqual('a""b', [['a""b']]) - - def test_inline_quotes(self): - self.readerAssertEqual('a"b"c', [['a"b"c']]) - - def test_quotes_and_more(self): - # Excel would never write a field containing '"a"b', but when - # reading one, it will return 'ab'. - self.readerAssertEqual('"a"b', [['ab']]) - - def test_lone_quote(self): - self.readerAssertEqual('a"b', [['a"b']]) - - def test_quote_and_quote(self): - # Excel would never write a field containing '"a" "b"', but when - # reading one, it will return 'a "b"'. - self.readerAssertEqual('"a" "b"', [['a "b"']]) - - def test_space_and_quote(self): - self.readerAssertEqual(' "a"', [[' "a"']]) - - def test_quoted(self): - self.readerAssertEqual('1,2,3,"I think, therefore I am",5,6', - [['1', '2', '3', - 'I think, therefore I am', - '5', '6']]) - - def test_quoted_quote(self): - self.readerAssertEqual('1,2,3,"""I see,"" said the blind man","as he picked up his hammer and saw"', - [['1', '2', '3', - '"I see," said the blind man', - 'as he picked up his hammer and saw']]) - - def test_quoted_nl(self): - input = '''\ -1,2,3,"""I see,"" -said the blind man","as he picked up his -hammer and saw" -9,8,7,6''' - self.readerAssertEqual(input, - [['1', '2', '3', - '"I see,"\nsaid the blind man', - 'as he picked up his\nhammer and saw'], - ['9','8','7','6']]) - - def test_dubious_quote(self): - self.readerAssertEqual('12,12,1",', [['12', '12', '1"', '']]) - - def test_null(self): - self.writerAssertEqual([], '') - - def test_single_writer(self): - self.writerAssertEqual([['abc']], 'abc\r\n') - - def test_simple_writer(self): - self.writerAssertEqual([[1, 2, 'abc', 3, 4]], '1,2,abc,3,4\r\n') - - def test_quotes(self): - self.writerAssertEqual([[1, 2, 'a"bc"', 3, 4]], '1,2,"a""bc""",3,4\r\n') - - def test_quote_fieldsep(self): - self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') - - def test_newlines(self): - self.writerAssertEqual([[1, 2, 'a\nbc', 3, 4]], '1,2,"a\nbc",3,4\r\n') - -class EscapedExcel(csv.excel): - quoting = csv.QUOTE_NONE - escapechar = '\\' - -class TestEscapedExcel(TestCsvBase): - dialect = EscapedExcel() - - def test_escape_fieldsep(self): - self.writerAssertEqual([['abc,def']], 'abc\\,def\r\n') - - def test_read_escape_fieldsep(self): - self.readerAssertEqual('abc\\,def\r\n', [['abc,def']]) - -class QuotedEscapedExcel(csv.excel): - quoting = csv.QUOTE_NONNUMERIC - escapechar = '\\' - -class TestQuotedEscapedExcel(TestCsvBase): - dialect = QuotedEscapedExcel() - - def test_write_escape_fieldsep(self): - self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') - - def test_read_escape_fieldsep(self): - self.readerAssertEqual('"abc\\,def"\r\n', [['abc,def']]) - -class TestDictFields(unittest.TestCase): - ### "long" means the row is longer than the number of fieldnames - ### "short" means there are fewer elements in the row than fieldnames - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_write_simple_dict(self): - fd, name = tempfile.mkstemp() - fileobj = io.open(fd, 'w+b') - try: - writer = csv.DictWriter(fileobj, fieldnames = ["f1", "f2", "f3"]) - writer.writeheader() - fileobj.seek(0) - self.assertEqual(fileobj.readline(), "f1,f2,f3\r\n") - writer.writerow({"f1": 10, "f3": "abc"}) - fileobj.seek(0) - fileobj.readline() # header - self.assertEqual(fileobj.read(), "10,,abc\r\n") - finally: - fileobj.close() - os.unlink(name) - - def test_write_no_fields(self): - fileobj = StringIO() - self.assertRaises(TypeError, csv.DictWriter, fileobj) - - def test_read_dict_fields(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames=["f1", "f2", "f3"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_dict_no_fieldnames(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("f1,f2,f3\r\n1,2,abc\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj) - self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - fileobj.close() - os.unlink(name) - - # Two test cases to make sure existing ways of implicitly setting - # fieldnames continue to work. Both arise from discussion in issue3436. - def test_read_dict_fieldnames_from_file(self): - fd, name = tempfile.mkstemp() - f = os.fdopen(fd, "w+b") - try: - f.write("f1,f2,f3\r\n1,2,abc\r\n") - f.seek(0) - reader = csv.DictReader(f, fieldnames=csv.reader(f).next()) - self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - f.close() - os.unlink(name) - - def test_read_dict_fieldnames_chain(self): - import itertools - fd, name = tempfile.mkstemp() - f = os.fdopen(fd, "w+b") - try: - f.write("f1,f2,f3\r\n1,2,abc\r\n") - f.seek(0) - reader = csv.DictReader(f) - first = next(reader) - for row in itertools.chain([first], reader): - self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) - self.assertEqual(row, {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - f.close() - os.unlink(name) - - def test_read_long(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc,4,5,6\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames=["f1", "f2"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', - None: ["abc", "4", "5", "6"]}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_long_with_rest(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc,4,5,6\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames=["f1", "f2"], restkey="_rest") - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', - "_rest": ["abc", "4", "5", "6"]}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_long_with_rest_no_fieldnames(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("f1,f2\r\n1,2,abc,4,5,6\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, restkey="_rest") - self.assertEqual(reader.fieldnames, ["f1", "f2"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', - "_rest": ["abc", "4", "5", "6"]}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_short(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc,4,5,6\r\n1,2,abc\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames="1 2 3 4 5 6".split(), - restval="DEFAULT") - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": 'DEFAULT', "5": 'DEFAULT', - "6": 'DEFAULT'}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_multi(self): - sample = [ - '2147483648,43.0e12,17,abc,def\r\n', - '147483648,43.0e2,17,abc,def\r\n', - '47483648,43.0,170,abc,def\r\n' - ] - - reader = csv.DictReader(sample, - fieldnames="i1 float i2 s1 s2".split()) - self.assertEqual(reader.next(), {"i1": '2147483648', - "float": '43.0e12', - "i2": '17', - "s1": 'abc', - "s2": 'def'}) - - def test_read_with_blanks(self): - reader = csv.DictReader(["1,2,abc,4,5,6\r\n","\r\n", - "1,2,abc,4,5,6\r\n"], - fieldnames="1 2 3 4 5 6".split()) - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - - def test_read_semi_sep(self): - reader = csv.DictReader(["1;2;abc;4;5;6\r\n"], - fieldnames="1 2 3 4 5 6".split(), - delimiter=';') - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - -class TestArrayWrites(unittest.TestCase): - def test_int_write(self): - import array - contents = [(20-i) for i in range(20)] - a = array.array('i', contents) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join([str(i) for i in a])+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_double_write(self): - import array - contents = [(20-i)*0.1 for i in range(20)] - a = array.array('d', contents) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join([repr(i) for i in a])+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_float_write(self): - import array - contents = [(20-i)*0.1 for i in range(20)] - a = array.array('f', contents) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join([repr(i) for i in a])+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - - def test_char_write(self): - import array, string - a = array.array('c', string.letters) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join(a)+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - -class TestDialectValidity(unittest.TestCase): - def test_quoting(self): - class mydialect(csv.Dialect): - delimiter = ";" - escapechar = '\\' - doublequote = False - skipinitialspace = True - lineterminator = '\r\n' - quoting = csv.QUOTE_NONE - d = mydialect() - - mydialect.quoting = None - self.assertRaises(csv.Error, mydialect) - - mydialect.doublequote = True - mydialect.quoting = csv.QUOTE_ALL - mydialect.quotechar = '"' - d = mydialect() - - mydialect.quotechar = "''" - self.assertRaises(csv.Error, mydialect) - - mydialect.quotechar = 4 - self.assertRaises(csv.Error, mydialect) - - def test_delimiter(self): - class mydialect(csv.Dialect): - delimiter = ";" - escapechar = '\\' - doublequote = False - skipinitialspace = True - lineterminator = '\r\n' - quoting = csv.QUOTE_NONE - d = mydialect() - - mydialect.delimiter = ":::" - self.assertRaises(csv.Error, mydialect) - - mydialect.delimiter = 4 - self.assertRaises(csv.Error, mydialect) - - def test_lineterminator(self): - class mydialect(csv.Dialect): - delimiter = ";" - escapechar = '\\' - doublequote = False - skipinitialspace = True - lineterminator = '\r\n' - quoting = csv.QUOTE_NONE - d = mydialect() - - mydialect.lineterminator = ":::" - d = mydialect() - - mydialect.lineterminator = 4 - self.assertRaises(csv.Error, mydialect) - - -class TestSniffer(unittest.TestCase): - sample1 = """\ -Harry's, Arlington Heights, IL, 2/1/03, Kimi Hayes -Shark City, Glendale Heights, IL, 12/28/02, Prezence -Tommy's Place, Blue Island, IL, 12/28/02, Blue Sunday/White Crow -Stonecutters Seafood and Chop House, Lemont, IL, 12/19/02, Week Back -""" - sample2 = """\ -'Harry''s':'Arlington Heights':'IL':'2/1/03':'Kimi Hayes' -'Shark City':'Glendale Heights':'IL':'12/28/02':'Prezence' -'Tommy''s Place':'Blue Island':'IL':'12/28/02':'Blue Sunday/White Crow' -'Stonecutters ''Seafood'' and Chop House':'Lemont':'IL':'12/19/02':'Week Back' -""" - header = '''\ -"venue","city","state","date","performers" -''' - sample3 = '''\ -05/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 -05/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 -05/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 -''' - - sample4 = '''\ -2147483648;43.0e12;17;abc;def -147483648;43.0e2;17;abc;def -47483648;43.0;170;abc;def -''' - - sample5 = "aaa\tbbb\r\nAAA\t\r\nBBB\t\r\n" - sample6 = "a|b|c\r\nd|e|f\r\n" - sample7 = "'a'|'b'|'c'\r\n'd'|e|f\r\n" - - def test_has_header(self): - sniffer = csv.Sniffer() - self.assertEqual(sniffer.has_header(self.sample1), False) - self.assertEqual(sniffer.has_header(self.header+self.sample1), True) - - def test_sniff(self): - sniffer = csv.Sniffer() - dialect = sniffer.sniff(self.sample1) - self.assertEqual(dialect.delimiter, ",") - self.assertEqual(dialect.quotechar, '"') - self.assertEqual(dialect.skipinitialspace, True) - - dialect = sniffer.sniff(self.sample2) - self.assertEqual(dialect.delimiter, ":") - self.assertEqual(dialect.quotechar, "'") - self.assertEqual(dialect.skipinitialspace, False) - - def test_delimiters(self): - sniffer = csv.Sniffer() - dialect = sniffer.sniff(self.sample3) - # given that all three lines in sample3 are equal, - # I think that any character could have been 'guessed' as the - # delimiter, depending on dictionary order - self.assertIn(dialect.delimiter, self.sample3) - dialect = sniffer.sniff(self.sample3, delimiters="?,") - self.assertEqual(dialect.delimiter, "?") - dialect = sniffer.sniff(self.sample3, delimiters="/,") - self.assertEqual(dialect.delimiter, "/") - dialect = sniffer.sniff(self.sample4) - self.assertEqual(dialect.delimiter, ";") - dialect = sniffer.sniff(self.sample5) - self.assertEqual(dialect.delimiter, "\t") - dialect = sniffer.sniff(self.sample6) - self.assertEqual(dialect.delimiter, "|") - dialect = sniffer.sniff(self.sample7) - self.assertEqual(dialect.delimiter, "|") - self.assertEqual(dialect.quotechar, "'") - - def test_doublequote(self): - sniffer = csv.Sniffer() - dialect = sniffer.sniff(self.header) - self.assertFalse(dialect.doublequote) - dialect = sniffer.sniff(self.sample2) - self.assertTrue(dialect.doublequote) - -if not hasattr(sys, "gettotalrefcount"): - if test_support.verbose: print "*** skipping leakage tests ***" -else: - class NUL: - def write(s, *args): - pass - writelines = write - - class TestLeaks(unittest.TestCase): - def test_create_read(self): - delta = 0 - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - delta = rc-lastrc - lastrc = rc - # if csv.reader() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) - - def test_create_write(self): - delta = 0 - lastrc = sys.gettotalrefcount() - s = NUL() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.writer(s) - csv.writer(s) - csv.writer(s) - delta = rc-lastrc - lastrc = rc - # if csv.writer() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) - - def test_read(self): - delta = 0 - rows = ["a,b,c\r\n"]*5 - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - rdr = csv.reader(rows) - for row in rdr: - pass - delta = rc-lastrc - lastrc = rc - # if reader leaks during read, delta should be 5 or more - self.assertEqual(delta < 5, True) - - def test_write(self): - delta = 0 - rows = [[1,2,3]]*5 - s = NUL() - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - writer = csv.writer(s) - for row in rows: - writer.writerow(row) - delta = rc-lastrc - lastrc = rc - # if writer leaks during write, last delta should be 5 or more - self.assertEqual(delta < 5, True) - -# commented out for now - csv module doesn't yet support Unicode -## class TestUnicode(unittest.TestCase): -## def test_unicode_read(self): -## import codecs -## f = codecs.EncodedFile(StringIO("Martin von L?wis," -## "Marc Andr? Lemburg," -## "Guido van Rossum," -## "Fran?ois Pinard\r\n"), -## data_encoding='iso-8859-1') -## reader = csv.reader(f) -## self.assertEqual(list(reader), [[u"Martin von L?wis", -## u"Marc Andr? Lemburg", -## u"Guido van Rossum", -## u"Fran?ois Pinardn"]]) - -def test_main(): - mod = sys.modules[__name__] - test_support.run_unittest( - *[getattr(mod, name) for name in dir(mod) if name.startswith('Test')] - ) - -if __name__ == '__main__': - test_main() diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7a3 Bugs Fixed + - [ 1968 ] Fixes for test_csv.py. - [ 1989 ] condition.notify_all() is missing. - [ 1994 ] threading.Event doesn't have is_set method fixed. - [ 1327 ] ThreadState needs API cleanup work diff --git a/src/org/python/modules/_csv/PyWriter.java b/src/org/python/modules/_csv/PyWriter.java --- a/src/org/python/modules/_csv/PyWriter.java +++ b/src/org/python/modules/_csv/PyWriter.java @@ -3,6 +3,7 @@ import org.python.core.Py; import org.python.core.PyException; +import org.python.core.PyFloat; import org.python.core.PyObject; import org.python.core.PyString; import org.python.core.PyType; @@ -130,7 +131,16 @@ } else if (field == Py.None) { append_ok = join_append("", len == 1); } else { - PyObject str = field.__str__(); + + PyObject str; + //XXX: in 3.x this check can go away and we can just always use + // __str__ + if (field.getClass() == PyFloat.class) { + str = field.__repr__(); + } else { + str = field.__str__(); + } + if (str == null) { return false; } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 5 20:30:29 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 5 Feb 2013 20:30:29 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?b?KTogTWVyZ2Uu?= Message-ID: <3Z0wqT4Cg9zSVY@mail.python.org> http://hg.python.org/jython/rev/bdfafb20e1fe changeset: 6997:bdfafb20e1fe parent: 6996:faf635092bdb parent: 6995:a81d0ea19f7b user: Frank Wierzbicki date: Tue Feb 05 11:30:15 2013 -0800 summary: Merge. files: Lib/socket.py | 19 +++++++++++++++---- 1 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -725,9 +725,11 @@ # Needed because urllib2 refers to it # + at raises_java_exception def gethostbyname_ex(name): return (name, [], gethostbyname(name)) + at raises_java_exception def gethostbyaddr(name): names, addrs = _gethostbyaddr(name) return (names[0], names, addrs) @@ -1029,6 +1031,7 @@ proto = "udp" return getservbyport(port, proto) + at raises_java_exception def getnameinfo(sock_addr, flags): if not isinstance(sock_addr, tuple) or len(sock_addr) < 2: raise TypeError("getnameinfo() argument 1 must be a tuple") @@ -1237,6 +1240,7 @@ return _nonblocking_api_mixin.getsockopt(self, level, optname) @raises_error + @raises_java_exception def bind(self, addr): assert not self.sock_impl assert not self.local_addr @@ -1270,8 +1274,6 @@ cliconn._setup() return cliconn, new_sock.getpeername() - @raises_error - @raises_java_exception def _do_connect(self, addr): assert not self.sock_impl self.sock_impl = _client_socket_impl() @@ -1281,11 +1283,15 @@ self._config() # Configure timeouts, etc, now that the socket exists self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) + @raises_error + @raises_java_exception def connect(self, addr): "This signifies a client socket" self._do_connect(addr) self._setup() + @raises_error + @raises_java_exception def connect_ex(self, addr): "This signifies a client socket" if not self.sock_impl: @@ -1320,6 +1326,8 @@ data = data[:m] return data.tostring() + @raises_error + @raises_java_exception def recvfrom(self, n): return self.recv(n), None @@ -1369,8 +1377,6 @@ self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) self._config() - @raises_error - @raises_java_exception def _do_connect(self, addr): assert not self.connected, "Datagram Socket is already connected" if not self.sock_impl: @@ -1379,9 +1385,13 @@ self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) self.connected = True + @raises_error + @raises_java_exception def connect(self, addr): self._do_connect(addr) + @raises_error + @raises_java_exception def connect_ex(self, addr): if not self.sock_impl: self._do_connect(addr) @@ -1401,6 +1411,7 @@ result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, self.family, self.type, self.proto, 0), flags) return result + @raises_java_exception def send(self, data, flags=None): if not self.connected: raise error(errno.ENOTCONN, "Socket is not connected") byte_array = java.lang.String(data).getBytes('iso-8859-1') -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 6 18:11:45 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 6 Feb 2013 18:11:45 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231716_Fixes_xrange_slicin?= =?utf-8?q?g_raises_NPE=2E_Thanks_Andreas_St=C3=BChrkf!?= Message-ID: <3Z1Thx3pfpzSQh@mail.python.org> http://hg.python.org/jython/rev/d77503b7a755 changeset: 6998:d77503b7a755 user: Frank Wierzbicki date: Wed Feb 06 09:11:32 2013 -0800 summary: #1716 Fixes xrange slicing raises NPE. Thanks Andreas St?hrkf! files: NEWS | 3 ++- src/org/python/core/PyXRange.java | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -1,7 +1,8 @@ Jython NEWS -Jython 2.7a3 +Jython 2.7b1 Bugs Fixed + - [ 1716 ] xrange slicing raises NPE. - [ 1968 ] Fixes for test_csv.py. - [ 1989 ] condition.notify_all() is missing. - [ 1994 ] threading.Event doesn't have is_set method fixed. diff --git a/src/org/python/core/PyXRange.java b/src/org/python/core/PyXRange.java --- a/src/org/python/core/PyXRange.java +++ b/src/org/python/core/PyXRange.java @@ -127,8 +127,7 @@ @Override protected PyObject getslice(int start, int stop, int step) { - // not supported - return null; + throw Py.TypeError("xrange index must be integer, not 'slice'"); } @Override -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 6 19:22:23 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 6 Feb 2013 19:22:23 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiAjMTcxNiBCYWNrcG9y?= =?utf-8?q?t_fix=3A_xrange_slicing_raises_NPE=2E_Thanks_Andreas_St=C3=BChr?= =?utf-8?q?kf!?= Message-ID: <3Z1WGR55LbzMXV@mail.python.org> http://hg.python.org/jython/rev/3345d6e15c79 changeset: 6999:3345d6e15c79 branch: 2.5 parent: 6884:4c3155645812 user: Frank Wierzbicki date: Wed Feb 06 10:08:46 2013 -0800 summary: #1716 Backport fix: xrange slicing raises NPE. Thanks Andreas St?hrkf! files: NEWS | 5 +++++ src/org/python/core/PyXRange.java | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -3,7 +3,12 @@ Jython 2.7a1 Bugs Fixed +Jython 2.5.4rc1 + Bugs Fixed + - [ 1716 ] xrange slicing raises NPE. + Jython 2.5.3rc1 + Bugs Fixed - [ 1952 ] __import__(): Handling of non-dict globals argument incompatible with CPython - [ 1900 ] Python imports from Java cause some Python imports to fail diff --git a/src/org/python/core/PyXRange.java b/src/org/python/core/PyXRange.java --- a/src/org/python/core/PyXRange.java +++ b/src/org/python/core/PyXRange.java @@ -127,8 +127,7 @@ @Override protected PyObject getslice(int start, int stop, int step) { - // not supported - return null; + throw Py.TypeError("xrange index must be integer, not 'slice'"); } @Override -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 6 19:22:25 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 6 Feb 2013 19:22:25 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z1WGT3DYCzSHs@mail.python.org> http://hg.python.org/jython/rev/353121061da9 changeset: 7000:353121061da9 parent: 6998:d77503b7a755 parent: 6999:3345d6e15c79 user: Frank Wierzbicki date: Wed Feb 06 10:09:41 2013 -0800 summary: Merge 2.5. files: NEWS | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -35,7 +35,12 @@ Bugs Fixed - [ 1880 ] Sha 224 library not present in Jython +Jython 2.5.4rc1 + Bugs Fixed + - [ 1716 ] xrange slicing raises NPE. + Jython 2.5.3rc1 + Bugs Fixed - [ 1952 ] __import__(): Handling of non-dict globals argument incompatible with CPython - [ 1900 ] Python imports from Java cause some Python imports to fail -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 6 21:11:45 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 6 Feb 2013 21:11:45 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiAjMTQ4MSBGaXggSWxs?= =?utf-8?q?egalArgumentException_instead_of_ValueError=2E?= Message-ID: <3Z1Yhd1Nf3zRKL@mail.python.org> http://hg.python.org/jython/rev/328c4e80117f changeset: 7001:328c4e80117f branch: 2.5 parent: 6999:3345d6e15c79 user: Frank Wierzbicki date: Wed Feb 06 12:09:54 2013 -0800 summary: #1481 Fix IllegalArgumentException instead of ValueError. Thanks Andreas St??hrk! files: ACKNOWLEDGMENTS | 1 + NEWS | 1 + src/org/python/modules/time/Time.java | 5 +++++ 3 files changed, 7 insertions(+), 0 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -93,6 +93,7 @@ Costantino Cerbo Alex Groenholm Anselm Kruis + Andreas St?hrk Local Variables: mode: indented-text diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1481 ] jython throws java.lang.IllegalArgumentException instead of ValueError. - [ 1716 ] xrange slicing raises NPE. Jython 2.5.3rc1 diff --git a/src/org/python/modules/time/Time.java b/src/org/python/modules/time/Time.java --- a/src/org/python/modules/time/Time.java +++ b/src/org/python/modules/time/Time.java @@ -780,6 +780,11 @@ builder.append("'"); inQuote = needsQuote; } + if (charAt == '\'') { + // a single quote always needs to be escaped, regardless + // whether already in a quote or not + builder.append("'"); + } builder.append(charAt); continue; } else if (inQuote) { -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 6 23:06:10 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 6 Feb 2013 23:06:10 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z1cDf2R0pzSWF@mail.python.org> http://hg.python.org/jython/rev/44166daa989b changeset: 7002:44166daa989b parent: 7000:353121061da9 parent: 7001:328c4e80117f user: Frank Wierzbicki date: Wed Feb 06 14:05:50 2013 -0800 summary: Merge 2.5. files: ACKNOWLEDGMENTS | 1 + NEWS | 1 + src/org/python/modules/time/Time.java | 5 +++++ 3 files changed, 7 insertions(+), 0 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -98,6 +98,7 @@ Jeff Allen Julian Kennedy Arfrever Frehtes Taifersar Arahesis + Andreas St?hrk Local Variables: mode: indented-text diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -37,6 +37,7 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1481 ] jython throws java.lang.IllegalArgumentException instead of ValueError. - [ 1716 ] xrange slicing raises NPE. Jython 2.5.3rc1 diff --git a/src/org/python/modules/time/Time.java b/src/org/python/modules/time/Time.java --- a/src/org/python/modules/time/Time.java +++ b/src/org/python/modules/time/Time.java @@ -780,6 +780,11 @@ builder.append("'"); inQuote = needsQuote; } + if (charAt == '\'') { + // a single quote always needs to be escaped, regardless + // whether already in a quote or not + builder.append("'"); + } builder.append(charAt); continue; } else if (inQuote) { -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Feb 7 00:14:42 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 7 Feb 2013 00:14:42 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiAjMTY3NiBmaXhlZCBO?= =?utf-8?q?PE_in_defaultdict=2E_Thanks_Geoffrey_French_for_initial_patch!?= Message-ID: <3Z1dlk3d8NzSTj@mail.python.org> http://hg.python.org/jython/rev/9124160150ae changeset: 7003:9124160150ae branch: 2.5 parent: 7001:328c4e80117f user: Frank Wierzbicki date: Wed Feb 06 14:53:22 2013 -0800 summary: #1676 fixed NPE in defaultdict. Thanks Geoffrey French for initial patch! files: Lib/test/test_dict_jy.py | 7 ++++ NEWS | 1 + src/org/python/core/PyDictionary.java | 23 ++++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_dict_jy.py b/Lib/test/test_dict_jy.py --- a/Lib/test/test_dict_jy.py +++ b/Lib/test/test_dict_jy.py @@ -1,6 +1,7 @@ from test import test_support import java import unittest +from collections import defaultdict class DictInitTest(unittest.TestCase): def testInternalSetitemInInit(self): @@ -93,6 +94,12 @@ def __getitem__(self, key): raise CustomKeyError("custom message") self.assertRaises(CustomKeyError, lambda: DerivedDict()['foo']) + + def test_issue1676(self): + #See http://bugs.jython.org/issue1676 + x=defaultdict() + #This formerly caused an NPE. + self.assertEqual(None, x.pop(None,None)) class JavaIntegrationTest(unittest.TestCase): "Tests for instantiating dicts from Java maps and hashtables" diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1676 ] NPE in defaultdict - [ 1481 ] jython throws java.lang.IllegalArgumentException instead of ValueError. - [ 1716 ] xrange slicing raises NPE. diff --git a/src/org/python/core/PyDictionary.java b/src/org/python/core/PyDictionary.java --- a/src/org/python/core/PyDictionary.java +++ b/src/org/python/core/PyDictionary.java @@ -31,10 +31,10 @@ public static final PyType TYPE = PyType.fromClass(PyDictionary.class); - private final ConcurrentMap map; + private final ConcurrentMap internalMap; public ConcurrentMap getMap() { - return map; + return internalMap; } /** @@ -49,8 +49,8 @@ */ public PyDictionary(PyType type, int capacity) { super(type); - map = new ConcurrentHashMap(capacity, Generic.CHM_LOAD_FACTOR, - Generic.CHM_CONCURRENCY_LEVEL); + internalMap = new ConcurrentHashMap(capacity, Generic.CHM_LOAD_FACTOR, + Generic.CHM_CONCURRENCY_LEVEL); } /** @@ -58,7 +58,7 @@ */ public PyDictionary(PyType type) { super(type); - map = Generic.concurrentMap(); + internalMap = Generic.concurrentMap(); } /** @@ -74,7 +74,7 @@ public PyDictionary(PyType type, Map map) { this(type, Math.max((int) (map.size() / Generic.CHM_LOAD_FACTOR) + 1, Generic.CHM_INITIAL_CAPACITY)); - this.map.putAll(map); + getMap().putAll(map); } /** @@ -85,9 +85,9 @@ protected PyDictionary(PyType type, boolean initializeBacking) { super(type); if (initializeBacking) { - map = Generic.concurrentMap(); + internalMap = Generic.concurrentMap(); } else { - map = null; // for later initialization + internalMap = null; // for later initialization } } @@ -100,6 +100,7 @@ */ public PyDictionary(PyObject elements[]) { this(); + ConcurrentMap map = getMap(); for (int i = 0; i < elements.length; i += 2) { map.put(elements[i], elements[i + 1]); } @@ -599,7 +600,7 @@ @ExposedMethod(defaults = "null", doc = BuiltinDocs.dict_pop_doc) final PyObject dict_pop(PyObject key, PyObject defaultValue) { - if (!map.containsKey(key)) { + if (!getMap().containsKey(key)) { if (defaultValue == null) { throw Py.KeyError("popitem(): dictionary is empty"); } @@ -713,7 +714,9 @@ return false; } final PyDictionary other = (PyDictionary) obj; - if (this.map != other.map && (this.map == null || !this.map.equals(other.map))) { + ConcurrentMap map = getMap(); + ConcurrentMap otherMap = other.getMap(); + if (map != otherMap && (map == null || !map.equals(otherMap))) { return false; } return true; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Feb 7 00:14:44 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 7 Feb 2013 00:14:44 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z1dlm0NG3zSW4@mail.python.org> http://hg.python.org/jython/rev/7a5ff7615be1 changeset: 7004:7a5ff7615be1 parent: 7002:44166daa989b parent: 7003:9124160150ae user: Frank Wierzbicki date: Wed Feb 06 15:14:27 2013 -0800 summary: Merge 2.5. files: Lib/test/test_dict_jy.py | 7 ++++ NEWS | 1 + src/org/python/core/PyDictionary.java | 23 ++++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_dict_jy.py b/Lib/test/test_dict_jy.py --- a/Lib/test/test_dict_jy.py +++ b/Lib/test/test_dict_jy.py @@ -1,6 +1,7 @@ from test import test_support import java import unittest +from collections import defaultdict class DictInitTest(unittest.TestCase): def testInternalSetitemInInit(self): @@ -93,6 +94,12 @@ def __getitem__(self, key): raise CustomKeyError("custom message") self.assertRaises(CustomKeyError, lambda: DerivedDict()['foo']) + + def test_issue1676(self): + #See http://bugs.jython.org/issue1676 + x=defaultdict() + #This formerly caused an NPE. + self.assertEqual(None, x.pop(None,None)) class JavaIntegrationTest(unittest.TestCase): "Tests for instantiating dicts from Java maps and hashtables" diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -37,6 +37,7 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1676 ] NPE in defaultdict - [ 1481 ] jython throws java.lang.IllegalArgumentException instead of ValueError. - [ 1716 ] xrange slicing raises NPE. diff --git a/src/org/python/core/PyDictionary.java b/src/org/python/core/PyDictionary.java --- a/src/org/python/core/PyDictionary.java +++ b/src/org/python/core/PyDictionary.java @@ -32,10 +32,10 @@ public static final PyType TYPE = PyType.fromClass(PyDictionary.class); - private final ConcurrentMap map; + private final ConcurrentMap internalMap; public ConcurrentMap getMap() { - return map; + return internalMap; } /** @@ -50,8 +50,8 @@ */ public PyDictionary(PyType type, int capacity) { super(type); - map = new ConcurrentHashMap(capacity, Generic.CHM_LOAD_FACTOR, - Generic.CHM_CONCURRENCY_LEVEL); + internalMap = new ConcurrentHashMap(capacity, Generic.CHM_LOAD_FACTOR, + Generic.CHM_CONCURRENCY_LEVEL); } /** @@ -59,7 +59,7 @@ */ public PyDictionary(PyType type) { super(type); - map = Generic.concurrentMap(); + internalMap = Generic.concurrentMap(); } /** @@ -75,7 +75,7 @@ public PyDictionary(PyType type, Map map) { this(type, Math.max((int) (map.size() / Generic.CHM_LOAD_FACTOR) + 1, Generic.CHM_INITIAL_CAPACITY)); - this.map.putAll(map); + getMap().putAll(map); } /** @@ -86,9 +86,9 @@ protected PyDictionary(PyType type, boolean initializeBacking) { super(type); if (initializeBacking) { - map = Generic.concurrentMap(); + internalMap = Generic.concurrentMap(); } else { - map = null; // for later initialization + internalMap = null; // for later initialization } } @@ -101,6 +101,7 @@ */ public PyDictionary(PyObject elements[]) { this(); + ConcurrentMap map = getMap(); for (int i = 0; i < elements.length; i += 2) { map.put(elements[i], elements[i + 1]); } @@ -600,7 +601,7 @@ @ExposedMethod(defaults = "null", doc = BuiltinDocs.dict_pop_doc) final PyObject dict_pop(PyObject key, PyObject defaultValue) { - if (!map.containsKey(key)) { + if (!getMap().containsKey(key)) { if (defaultValue == null) { throw Py.KeyError("popitem(): dictionary is empty"); } @@ -738,7 +739,9 @@ return false; } final PyDictionary other = (PyDictionary) obj; - if (this.map != other.map && (this.map == null || !this.map.equals(other.map))) { + ConcurrentMap map = getMap(); + ConcurrentMap otherMap = other.getMap(); + if (map != otherMap && (map == null || !map.equals(otherMap))) { return false; } return true; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Feb 7 21:49:44 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 7 Feb 2013 21:49:44 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiAjMTk2MiBGaXhlZDog?= =?utf-8?q?Interactive_console_in_Jython_2=2E5=2E3_needs_CRTL-D_to_execute?= =?utf-8?q?=2E?= Message-ID: <3Z2BV01jjczMb9@mail.python.org> http://hg.python.org/jython/rev/ba4ec099655d changeset: 7005:ba4ec099655d branch: 2.5 parent: 7003:9124160150ae user: Frank Wierzbicki date: Thu Feb 07 12:35:48 2013 -0800 summary: #1962 Fixed: Interactive console in Jython 2.5.3 needs CRTL-D to execute. Reverted StringIO change and test_io_jy. files: Lib/test/test_io_jy.py | 66 ---------------- src/org/python/core/io/StreamIO.java | 58 +------------- 2 files changed, 1 insertions(+), 123 deletions(-) diff --git a/Lib/test/test_io_jy.py b/Lib/test/test_io_jy.py deleted file mode 100644 --- a/Lib/test/test_io_jy.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Misc io tests. - -Made for Jython. -""" -import unittest - -from org.python.core.util import FileUtil -from org.python.core.io import StreamIO - -from java.io import InputStream -from java.nio import ByteBuffer; - -class InfiniteInputStream(InputStream): - - def read(self, *args): - if len(args) == 0: - return ord('x') - elif len(args) == 1: - return InputStream.read(self, args[0]) - else: - return self.read_buffer(*args) - - def read_buffer(self, buf, off, length): - if length > 0: - buf[off] = ord('x') - return 1 - return 0 - - -class IoTestCase(unittest.TestCase): - """ - Jython was failing to read all available content when an InputStream - returns early. Java's InputStream.read() is allowed to return less than the - requested # of bytes under non-exceptional/EOF conditions, whereas - (for example) wsgi.input requires the file.read() method to block until the - requested # of bytes are available (except for exceptional/EOF conditions). - - See http://bugs.jython.org/issue1754 for more discussion. - """ - def test_infinite_input(self): - iis = InfiniteInputStream() - f = FileUtil.wrap(iis, 'rb') - size = 10000 - self.assertEqual(len(f.read(size)), size) - self.assertEqual(len(f.read(size)), size) - self.assertEqual(len(f.read(size)), size) - - def test_buffer_no_array(self): - """ - Directly tests StreamIO with and without a backing array and an - InputStream that returns early. - """ - size = 10000 - without_array = ByteBuffer.allocateDirect(size) - self.assertFalse(without_array.hasArray()) - with_array = ByteBuffer.allocate(size) - self.assertTrue(with_array.hasArray()) - bbs = [with_array, without_array] - for bb in bbs: - iis = InfiniteInputStream() - io = StreamIO(iis, True) - self.assertEqual(io.readinto(bb), size) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/org/python/core/io/StreamIO.java b/src/org/python/core/io/StreamIO.java --- a/src/org/python/core/io/StreamIO.java +++ b/src/org/python/core/io/StreamIO.java @@ -276,63 +276,7 @@ } private static ReadableByteChannel newChannel(InputStream in) { - return new InternalReadableByteChannel(in); - } - - - /* - * AbstractInterruptibleChannel is used for its end() and begin() implementations - * but this Channel is not really interruptible. - */ - private static class InternalReadableByteChannel - extends AbstractInterruptibleChannel - implements ReadableByteChannel { - - private InputStream in; - private boolean open = true; - - InternalReadableByteChannel(InputStream in) { - this.in = in; - } - - public int read(ByteBuffer dst) throws IOException { - final int CHUNK = 8192; - - int len = dst.remaining(); - int totalRead = 0; - int bytesRead = 0; - - byte buf[] = new byte[0]; - while (totalRead < len) { - int bytesToRead = Math.min((len - totalRead), CHUNK); - if (buf.length < bytesToRead) { - buf = new byte[bytesToRead]; - } - try { - begin(); - bytesRead = in.read(buf, 0, bytesToRead); - } finally { - end(bytesRead > 0); - } - if (bytesRead < 0) { - break; - } else { - totalRead += bytesRead; - } - dst.put(buf, 0, bytesRead); - } - if ((bytesRead < 0) && (totalRead == 0)) { - return -1; - } - return totalRead; - } - - protected void implCloseChannel() throws IOException { - if (open) { - in.close(); - open = false; - } - } + return Channels.newChannel(in); } } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Feb 7 21:49:45 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 7 Feb 2013 21:49:45 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z2BV15RzPzSL4@mail.python.org> http://hg.python.org/jython/rev/fe321decfbe9 changeset: 7006:fe321decfbe9 parent: 7004:7a5ff7615be1 parent: 7005:ba4ec099655d user: Frank Wierzbicki date: Thu Feb 07 12:36:31 2013 -0800 summary: Merge 2.5. files: Lib/test/test_io_jy.py | 66 ---------------- src/org/python/core/io/StreamIO.java | 58 +------------- 2 files changed, 1 insertions(+), 123 deletions(-) diff --git a/Lib/test/test_io_jy.py b/Lib/test/test_io_jy.py deleted file mode 100644 --- a/Lib/test/test_io_jy.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Misc io tests. - -Made for Jython. -""" -import unittest - -from org.python.core.util import FileUtil -from org.python.core.io import StreamIO - -from java.io import InputStream -from java.nio import ByteBuffer; - -class InfiniteInputStream(InputStream): - - def read(self, *args): - if len(args) == 0: - return ord('x') - elif len(args) == 1: - return InputStream.read(self, args[0]) - else: - return self.read_buffer(*args) - - def read_buffer(self, buf, off, length): - if length > 0: - buf[off] = ord('x') - return 1 - return 0 - - -class IoTestCase(unittest.TestCase): - """ - Jython was failing to read all available content when an InputStream - returns early. Java's InputStream.read() is allowed to return less than the - requested # of bytes under non-exceptional/EOF conditions, whereas - (for example) wsgi.input requires the file.read() method to block until the - requested # of bytes are available (except for exceptional/EOF conditions). - - See http://bugs.jython.org/issue1754 for more discussion. - """ - def test_infinite_input(self): - iis = InfiniteInputStream() - f = FileUtil.wrap(iis, 'rb') - size = 10000 - self.assertEqual(len(f.read(size)), size) - self.assertEqual(len(f.read(size)), size) - self.assertEqual(len(f.read(size)), size) - - def test_buffer_no_array(self): - """ - Directly tests StreamIO with and without a backing array and an - InputStream that returns early. - """ - size = 10000 - without_array = ByteBuffer.allocateDirect(size) - self.assertFalse(without_array.hasArray()) - with_array = ByteBuffer.allocate(size) - self.assertTrue(with_array.hasArray()) - bbs = [with_array, without_array] - for bb in bbs: - iis = InfiniteInputStream() - io = StreamIO(iis, True) - self.assertEqual(io.readinto(bb), size) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/org/python/core/io/StreamIO.java b/src/org/python/core/io/StreamIO.java --- a/src/org/python/core/io/StreamIO.java +++ b/src/org/python/core/io/StreamIO.java @@ -276,63 +276,7 @@ } private static ReadableByteChannel newChannel(InputStream in) { - return new InternalReadableByteChannel(in); - } - - - /* - * AbstractInterruptibleChannel is used for its end() and begin() implementations - * but this Channel is not really interruptible. - */ - private static class InternalReadableByteChannel - extends AbstractInterruptibleChannel - implements ReadableByteChannel { - - private InputStream in; - private boolean open = true; - - InternalReadableByteChannel(InputStream in) { - this.in = in; - } - - public int read(ByteBuffer dst) throws IOException { - final int CHUNK = 8192; - - int len = dst.remaining(); - int totalRead = 0; - int bytesRead = 0; - - byte buf[] = new byte[0]; - while (totalRead < len) { - int bytesToRead = Math.min((len - totalRead), CHUNK); - if (buf.length < bytesToRead) { - buf = new byte[bytesToRead]; - } - try { - begin(); - bytesRead = in.read(buf, 0, bytesToRead); - } finally { - end(bytesRead > 0); - } - if (bytesRead < 0) { - break; - } else { - totalRead += bytesRead; - } - dst.put(buf, 0, bytesRead); - } - if ((bytesRead < 0) && (totalRead == 0)) { - return -1; - } - return totalRead; - } - - protected void implCloseChannel() throws IOException { - if (open) { - in.close(); - open = false; - } - } + return Channels.newChannel(in); } } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Feb 7 21:57:43 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 7 Feb 2013 21:57:43 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_Update_NEWS=2E?= Message-ID: <3Z2BgC6K4DzSL4@mail.python.org> http://hg.python.org/jython/rev/b720649748d3 changeset: 7007:b720649748d3 branch: 2.5 parent: 7005:ba4ec099655d user: Frank Wierzbicki date: Thu Feb 07 12:57:15 2013 -0800 summary: Update NEWS. files: NEWS | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1972 ] jython 2.5.3 sys.stdin.readline() hangs when jython launched as subprocess on Mac OS X. + - [ 1962 ] Interactive console in Jython 2.5.3 needs CRTL-D to execute command. - [ 1676 ] NPE in defaultdict - [ 1481 ] jython throws java.lang.IllegalArgumentException instead of ValueError. - [ 1716 ] xrange slicing raises NPE. -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Feb 7 21:57:45 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 7 Feb 2013 21:57:45 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z2BgF281hzSZT@mail.python.org> http://hg.python.org/jython/rev/d9c03da9857f changeset: 7008:d9c03da9857f parent: 7006:fe321decfbe9 parent: 7007:b720649748d3 user: Frank Wierzbicki date: Thu Feb 07 12:57:29 2013 -0800 summary: Merge 2.5. files: NEWS | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -37,6 +37,8 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1972 ] jython 2.5.3 sys.stdin.readline() hangs when jython launched as subprocess on Mac OS X. + - [ 1962 ] Interactive console in Jython 2.5.3 needs CRTL-D to execute command. - [ 1676 ] NPE in defaultdict - [ 1481 ] jython throws java.lang.IllegalArgumentException instead of ValueError. - [ 1716 ] xrange slicing raises NPE. -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 8 01:03:05 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 8 Feb 2013 01:03:05 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiAjMTkzNiBGaXhlZDog?= =?utf-8?q?JBoss_7=2C_vfs_protocol_in_use_for_jarFileName_in_PySystemState?= =?utf-8?q?=2E?= Message-ID: <3Z2Gn53T3XzSdZ@mail.python.org> http://hg.python.org/jython/rev/df829bcbd4f3 changeset: 7009:df829bcbd4f3 branch: 2.5 parent: 7007:b720649748d3 user: Frank Wierzbicki date: Thu Feb 07 15:51:28 2013 -0800 summary: #1936 Fixed: JBoss 7, vfs protocol in use for jarFileName in PySystemState. files: ACKNOWLEDGMENTS | 1 + NEWS | 1 + src/org/python/core/PySystemState.java | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 0 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -94,6 +94,7 @@ Alex Groenholm Anselm Kruis Andreas St?hrk + Olivier Heurtier Local Variables: mode: indented-text diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. - [ 1972 ] jython 2.5.3 sys.stdin.readline() hangs when jython launched as subprocess on Mac OS X. - [ 1962 ] Interactive console in Jython 2.5.3 needs CRTL-D to execute command. - [ 1676 ] NPE in defaultdict diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java --- a/src/org/python/core/PySystemState.java +++ b/src/org/python/core/PySystemState.java @@ -54,6 +54,7 @@ private static final String JAR_URL_PREFIX = "jar:file:"; private static final String JAR_SEPARATOR = "!"; private static final String VFSZIP_PREFIX = "vfszip:"; + private static final String VFS_PREFIX = "vfs:"; public static final PyString version = new PyString(Version.getVersion()); public static final int hexversion = ((Version.PY_MAJOR_VERSION << 24) | @@ -1148,6 +1149,19 @@ } jarFileName = urlString.substring(start, jarIndex); } + } else if (urlString.startsWith(VFS_PREFIX)) { + // vfs:/some/path/jython.jar/org/python/core/PySystemState.class + final String path = PySystemState.class.getName().replace('.', '/'); + int jarIndex = urlString.indexOf(".jar/".concat(path)); + if (jarIndex > 0) { + jarIndex += 4; + int start = VFS_PREFIX.length(); + if (Platform.IS_WINDOWS) { + // vfs:/C:/some/path/jython.jar/org/python/core/PySystemState.class + start++; + } + jarFileName = urlString.substring(start, jarIndex); + } } } catch (Exception e) {} } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 8 01:03:06 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 8 Feb 2013 01:03:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z2Gn663b8zSd7@mail.python.org> http://hg.python.org/jython/rev/f29453ef0415 changeset: 7010:f29453ef0415 parent: 7008:d9c03da9857f parent: 7009:df829bcbd4f3 user: Frank Wierzbicki date: Thu Feb 07 16:02:49 2013 -0800 summary: Merge 2.5. files: ACKNOWLEDGMENTS | 1 + NEWS | 1 + src/org/python/core/PySystemState.java | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 0 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -93,6 +93,7 @@ Costantino Cerbo Alex Groenholm Anselm Kruis + Andreas St?hrk Dmitry Jemerov Miki Tebeka Jeff Allen diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -37,6 +37,7 @@ Jython 2.5.4rc1 Bugs Fixed + - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. - [ 1972 ] jython 2.5.3 sys.stdin.readline() hangs when jython launched as subprocess on Mac OS X. - [ 1962 ] Interactive console in Jython 2.5.3 needs CRTL-D to execute command. - [ 1676 ] NPE in defaultdict diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java --- a/src/org/python/core/PySystemState.java +++ b/src/org/python/core/PySystemState.java @@ -59,6 +59,7 @@ private static final String JAR_URL_PREFIX = "jar:file:"; private static final String JAR_SEPARATOR = "!"; private static final String VFSZIP_PREFIX = "vfszip:"; + private static final String VFS_PREFIX = "vfs:"; public static final PyString version = new PyString(Version.getVersion()); @@ -1181,6 +1182,19 @@ } jarFileName = urlString.substring(start, jarIndex); } + } else if (urlString.startsWith(VFS_PREFIX)) { + // vfs:/some/path/jython.jar/org/python/core/PySystemState.class + final String path = PySystemState.class.getName().replace('.', '/'); + int jarIndex = urlString.indexOf(".jar/".concat(path)); + if (jarIndex > 0) { + jarIndex += 4; + int start = VFS_PREFIX.length(); + if (Platform.IS_WINDOWS) { + // vfs:/C:/some/path/jython.jar/org/python/core/PySystemState.class + start++; + } + jarFileName = urlString.substring(start, jarIndex); + } } } catch (Exception e) {} } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 8 18:24:41 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 8 Feb 2013 18:24:41 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_Prepare_for_2?= =?utf-8?q?=2E5=2E4rc1_release=2E?= Message-ID: <3Z2jtx0VlwzNgq@mail.python.org> http://hg.python.org/jython/rev/f5b12dc4ff97 changeset: 7011:f5b12dc4ff97 branch: 2.5 tag: v2.5.4rc1 parent: 7009:df829bcbd4f3 user: Frank Wierzbicki date: Fri Feb 08 09:23:21 2013 -0800 summary: Prepare for 2.5.4rc1 release. files: README.txt | 12 +++++------- build.xml | 10 +++++----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/README.txt b/README.txt --- a/README.txt +++ b/README.txt @@ -1,20 +1,18 @@ Welcome to Jython 2.5.3! ======================== -This is the final release of Jython 2.5.3. +This is the first release candidate of Jython 2.5.4. Thanks to Adconion Media Group (http://www.adconion.com/) for sponsoring this release, and thanks to all who contribute to Jython. -This release fixes numerous bugs since the 2.5.2 release of Jython. Some +This release fixes numerous bugs since the 2.5.3 release of Jython. Some highlights include: -* File uploads where broken in Tomcat and Jetty. -* Imports sometimes blew the stack. This was seen in SQLAlchemy for example. -* Some race conditions and threading issues where fixed. -* Several JSR 223 problems have been fixed. +* The file upload fix for Tomcat and Jetty has been changed. +* Some NullPointerExceptions where fixed. Please see the NEWS file for detailed release notes. The release was compiled -on Ubuntu with JDK 6 and requires JDK 5 to run. +on Ubuntu with JDK 7 and requires JDK 5 to run. Please try this out and report any bugs at http://bugs.jython.org. diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -123,13 +123,13 @@ - - + + - - - + + + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 8 18:24:42 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 8 Feb 2013 18:24:42 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiBBZGRlZCB0YWcgdjIu?= =?utf-8?q?5=2E4rc1_for_changeset_f5b12dc4ff97?= Message-ID: <3Z2jty38qTzSf5@mail.python.org> http://hg.python.org/jython/rev/723492dbab02 changeset: 7012:723492dbab02 branch: 2.5 user: Frank Wierzbicki date: Fri Feb 08 09:24:13 2013 -0800 summary: Added tag v2.5.4rc1 for changeset f5b12dc4ff97 files: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -61,3 +61,4 @@ bc783e270abc80c94aab8fb55aa363de0d2422bb v2.5.3b3 458a07c2f0f05d6163017b893bee2a5a61489487 v2.5.3rc1 3d2dbae23c5292b7f31ac4c92fa6d1afd8bd8eb2 v2.5.3 +f5b12dc4ff970c9594c99fc895772958c4a669a0 v2.5.4rc1 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 8 21:01:15 2013 From: jython-checkins at python.org (philip.jenvey) Date: Fri, 8 Feb 2013 21:01:15 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_passthru_the_bl?= =?utf-8?q?ocking_arg_in_Condition_acquire?= Message-ID: <3Z2nMb5j1vzSdv@mail.python.org> http://hg.python.org/jython/rev/f9c6dc60c4eb changeset: 7013:f9c6dc60c4eb branch: 2.5 user: Philip Jenvey date: Fri Feb 08 11:59:21 2013 -0800 summary: passthru the blocking arg in Condition acquire fixes #1988 files: Lib/test/test_threading_jy.py | 9 ++++++ NEWS | 4 ++ src/org/python/modules/_threading/Condition.java | 14 ++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_threading_jy.py b/Lib/test/test_threading_jy.py --- a/Lib/test/test_threading_jy.py +++ b/Lib/test/test_threading_jy.py @@ -43,6 +43,15 @@ def _sleep(self, n): time.sleep(random.random()) + def test_issue1988(self): + cond = threading.Condition(threading.Lock()) + locked = False + try: + locked = cond.acquire(False) + finally: + if locked: + cond.release() + class TwistedTestCase(unittest.TestCase): diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -3,6 +3,10 @@ Jython 2.7a1 Bugs Fixed +Jython 2.5.4rc2 + Bugs Fixed + - [ 1988 ] API for threading.condition fails to accept *args for acquire + Jython 2.5.4rc1 Bugs Fixed - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. diff --git a/src/org/python/modules/_threading/Condition.java b/src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java +++ b/src/org/python/modules/_threading/Condition.java @@ -38,12 +38,16 @@ } public boolean acquire() { - return Condition_acquire(); + return Condition_acquire(true); } - @ExposedMethod - final boolean Condition_acquire() { - return _lock.acquire(); + public boolean acquire(boolean blocking) { + return Condition_acquire(blocking); + } + + @ExposedMethod(defaults = "true") + final boolean Condition_acquire(boolean blocking) { + return _lock.acquire(blocking); } public PyObject __enter__(ThreadState ts) { @@ -53,7 +57,7 @@ @ExposedMethod final PyObject Condition___enter__() { - Condition_acquire(); + Condition_acquire(true); return this; } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 8 21:01:17 2013 From: jython-checkins at python.org (philip.jenvey) Date: Fri, 8 Feb 2013 21:01:17 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?q?=3A_merge_2=2E5?= Message-ID: <3Z2nMd1YBpzSf5@mail.python.org> http://hg.python.org/jython/rev/a8b0a3007ed3 changeset: 7014:a8b0a3007ed3 parent: 7010:f29453ef0415 parent: 7013:f9c6dc60c4eb user: Philip Jenvey date: Fri Feb 08 12:00:57 2013 -0800 summary: merge 2.5 files: .hgtags | 1 + Lib/test/test_threading_jy.py | 9 ++++++ NEWS | 4 ++ src/org/python/modules/_threading/Condition.java | 14 ++++++--- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -63,3 +63,4 @@ 4f5e3c12edc040058d724d4469a53e8649e64420 v2.7.0a1 ac5609677c1382f14d0e04178fe6dd4108e4c231 v2.7.0a2 3d2dbae23c5292b7f31ac4c92fa6d1afd8bd8eb2 v2.5.3 +f5b12dc4ff970c9594c99fc895772958c4a669a0 v2.5.4rc1 diff --git a/Lib/test/test_threading_jy.py b/Lib/test/test_threading_jy.py --- a/Lib/test/test_threading_jy.py +++ b/Lib/test/test_threading_jy.py @@ -43,6 +43,15 @@ def _sleep(self, n): time.sleep(random.random()) + def test_issue1988(self): + cond = threading.Condition(threading.Lock()) + locked = False + try: + locked = cond.acquire(False) + finally: + if locked: + cond.release() + class TwistedTestCase(unittest.TestCase): diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -35,6 +35,10 @@ Bugs Fixed - [ 1880 ] Sha 224 library not present in Jython +Jython 2.5.4rc2 + Bugs Fixed + - [ 1988 ] API for threading.condition fails to accept *args for acquire + Jython 2.5.4rc1 Bugs Fixed - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. diff --git a/src/org/python/modules/_threading/Condition.java b/src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java +++ b/src/org/python/modules/_threading/Condition.java @@ -38,12 +38,16 @@ } public boolean acquire() { - return Condition_acquire(); + return Condition_acquire(true); } - @ExposedMethod - final boolean Condition_acquire() { - return _lock.acquire(); + public boolean acquire(boolean blocking) { + return Condition_acquire(blocking); + } + + @ExposedMethod(defaults = "true") + final boolean Condition_acquire(boolean blocking) { + return _lock.acquire(blocking); } public PyObject __enter__(ThreadState ts) { @@ -53,7 +57,7 @@ @ExposedMethod final PyObject Condition___enter__() { - Condition_acquire(); + Condition_acquire(true); return this; } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 02:05:44 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 9 Feb 2013 02:05:44 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_Move_installer_?= =?utf-8?q?into_mercurial_repository=2E?= Message-ID: <3Z2w6w5tyFzRdk@mail.python.org> http://hg.python.org/jython/rev/402cab975983 changeset: 7015:402cab975983 branch: 2.5 parent: 7012:723492dbab02 user: Oti Humbel date: Fri Feb 08 11:54:19 2013 -0800 summary: Move installer into mercurial repository. Committed with user Oti Humbel. files: installer/src/java/org/apache/LICENSE.txt | 202 +++ installer/src/java/org/apache/commons/cli/AlreadySelectedException.java | 81 + installer/src/java/org/apache/commons/cli/BasicParser.java | 92 + installer/src/java/org/apache/commons/cli/CommandLine.java | 328 +++++ installer/src/java/org/apache/commons/cli/CommandLineParser.java | 97 + installer/src/java/org/apache/commons/cli/GnuParser.java | 187 ++ installer/src/java/org/apache/commons/cli/HelpFormatter.java | 542 ++++++++ installer/src/java/org/apache/commons/cli/MissingArgumentException.java | 82 + installer/src/java/org/apache/commons/cli/MissingOptionException.java | 81 + installer/src/java/org/apache/commons/cli/Option.java | 575 +++++++++ installer/src/java/org/apache/commons/cli/OptionBuilder.java | 368 +++++ installer/src/java/org/apache/commons/cli/OptionGroup.java | 187 ++ installer/src/java/org/apache/commons/cli/Options.java | 331 +++++ installer/src/java/org/apache/commons/cli/ParseException.java | 82 + installer/src/java/org/apache/commons/cli/Parser.java | 282 ++++ installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java | 204 +++ installer/src/java/org/apache/commons/cli/PosixParser.java | 342 +++++ installer/src/java/org/apache/commons/cli/TypeHandler.java | 252 +++ installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java | 82 + installer/src/java/org/python/util/install/AbstractWizard.java | 438 ++++++ installer/src/java/org/python/util/install/AbstractWizardHeader.java | 12 + installer/src/java/org/python/util/install/AbstractWizardPage.java | 153 ++ installer/src/java/org/python/util/install/AbstractWizardValidator.java | 132 ++ installer/src/java/org/python/util/install/ChildProcess.java | 357 +++++ installer/src/java/org/python/util/install/ConsoleInstaller.java | 609 +++++++++ installer/src/java/org/python/util/install/DirectoryFilter.java | 23 + installer/src/java/org/python/util/install/DirectorySelectionPage.java | 200 +++ installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java | 36 + installer/src/java/org/python/util/install/EmptyValidator.java | 8 + installer/src/java/org/python/util/install/FileHelper.java | 208 +++ installer/src/java/org/python/util/install/FrameInstaller.java | 186 ++ installer/src/java/org/python/util/install/Installation.java | 439 ++++++ installer/src/java/org/python/util/install/InstallationCancelledException.java | 9 + installer/src/java/org/python/util/install/InstallationListener.java | 7 + installer/src/java/org/python/util/install/InstallationType.java | 120 + installer/src/java/org/python/util/install/InstallerCommandLine.java | 456 +++++++ installer/src/java/org/python/util/install/InstallerException.java | 21 + installer/src/java/org/python/util/install/JarInfo.java | 235 +++ installer/src/java/org/python/util/install/JarInstaller.java | 283 ++++ installer/src/java/org/python/util/install/JavaHomeHandler.java | 209 +++ installer/src/java/org/python/util/install/JavaSelectionPage.java | 199 +++ installer/src/java/org/python/util/install/JavaSelectionPageValidator.java | 31 + installer/src/java/org/python/util/install/JavaVersionTester.java | 61 + installer/src/java/org/python/util/install/LanguagePage.java | 121 + installer/src/java/org/python/util/install/LicensePage.java | 120 + installer/src/java/org/python/util/install/LicensePageValidator.java | 17 + installer/src/java/org/python/util/install/OverviewPage.java | 243 +++ installer/src/java/org/python/util/install/ProgressListener.java | 15 + installer/src/java/org/python/util/install/ProgressPage.java | 122 + installer/src/java/org/python/util/install/ReadmePage.java | 75 + installer/src/java/org/python/util/install/StandalonePackager.java | 184 ++ installer/src/java/org/python/util/install/StartScriptGenerator.java | 240 +++ installer/src/java/org/python/util/install/SuccessPage.java | 55 + installer/src/java/org/python/util/install/TextConstants.java | 148 ++ installer/src/java/org/python/util/install/TextConstants_de.java | 148 ++ installer/src/java/org/python/util/install/TextConstants_en.java | 5 + installer/src/java/org/python/util/install/TextKeys.java | 135 ++ installer/src/java/org/python/util/install/TypePage.java | 268 ++++ installer/src/java/org/python/util/install/UnicodeSequences.java | 19 + installer/src/java/org/python/util/install/ValidationEvent.java | 14 + installer/src/java/org/python/util/install/ValidationException.java | 19 + installer/src/java/org/python/util/install/ValidationInformationException.java | 21 + installer/src/java/org/python/util/install/ValidationListener.java | 11 + installer/src/java/org/python/util/install/Wizard.java | 90 + installer/src/java/org/python/util/install/WizardEvent.java | 13 + installer/src/java/org/python/util/install/WizardHeader.java | 89 + installer/src/java/org/python/util/install/WizardListener.java | 13 + installer/src/java/org/python/util/install/driver/Autotest.java | 274 ++++ installer/src/java/org/python/util/install/driver/ConsoleAutotest.java | 30 + installer/src/java/org/python/util/install/driver/ConsoleDriver.java | 58 + installer/src/java/org/python/util/install/driver/DriverException.java | 17 + installer/src/java/org/python/util/install/driver/GuiAutotest.java | 188 ++ installer/src/java/org/python/util/install/driver/InstallationDriver.java | 416 ++++++ installer/src/java/org/python/util/install/driver/NormalVerifier.java | 287 ++++ installer/src/java/org/python/util/install/driver/SilentAutotest.java | 25 + installer/src/java/org/python/util/install/driver/StandaloneVerifier.java | 74 + installer/src/java/org/python/util/install/driver/Tunnel.java | 61 + installer/src/java/org/python/util/install/driver/Verifier.java | 17 + installer/src/java/org/python/util/install/driver/jython_test.bat.template | 73 + installer/src/java/org/python/util/install/driver/jython_test.template | 58 + installer/src/java/org/python/util/install/jython_small_c.png | Bin installer/test/java/org/AllTests.java | 101 + installer/test/java/org/apache/commons/cli/ApplicationTest.java | 120 + installer/test/java/org/apache/commons/cli/BugsTest.java | 348 +++++ installer/test/java/org/apache/commons/cli/BuildTest.java | 96 + installer/test/java/org/apache/commons/cli/GnuParseTest.java | 265 ++++ installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java | 106 + installer/test/java/org/apache/commons/cli/HelpFormatterTest.java | 82 + installer/test/java/org/apache/commons/cli/OptionBuilderTest.java | 160 ++ installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java | 43 + installer/test/java/org/apache/commons/cli/OptionGroupTest.java | 246 +++ installer/test/java/org/apache/commons/cli/OptionsTest.java | 28 + installer/test/java/org/apache/commons/cli/ParseRequiredTest.java | 113 + installer/test/java/org/apache/commons/cli/ParseTest.java | 285 ++++ installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java | 82 + installer/test/java/org/apache/commons/cli/PosixParserTest.java | 138 ++ installer/test/java/org/apache/commons/cli/TestHelpFormatter.java | 170 ++ installer/test/java/org/apache/commons/cli/ValueTest.java | 271 ++++ installer/test/java/org/apache/commons/cli/ValuesTest.java | 248 +++ installer/test/java/org/python/util/install/ChildProcessExample.java | 32 + installer/test/java/org/python/util/install/ChildProcessTest.java | 80 + installer/test/java/org/python/util/install/ChmodTest_Standalone.java | 57 + installer/test/java/org/python/util/install/FileHelperTest.java | 271 ++++ installer/test/java/org/python/util/install/FrameInstallerTest.java | 88 + installer/test/java/org/python/util/install/InstallationTest.java | 107 + installer/test/java/org/python/util/install/InstallationTypeTest.java | 119 + installer/test/java/org/python/util/install/InstallerCommandLineTest.java | 637 ++++++++++ installer/test/java/org/python/util/install/JavaHomeHandlerTest.java | 114 + installer/test/java/org/python/util/install/JavaTest_Standalone.java | 32 + installer/test/java/org/python/util/install/StandalonePackagerTest.java | 183 ++ installer/test/java/org/python/util/install/StartScriptGeneratorTest.java | 277 ++++ installer/test/java/org/python/util/install/UnicodeSequencesTest.java | 34 + installer/test/java/org/python/util/install/driver/AutotestTest.java | 78 + installer/test/java/org/python/util/install/driver/DrivableConsole.java | 82 + installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java | 37 + installer/test/java/org/python/util/install/driver/NormalVerifierTest.java | 131 ++ installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java | 72 + 117 files changed, 17925 insertions(+), 0 deletions(-) diff --git a/installer/src/java/org/apache/LICENSE.txt b/installer/src/java/org/apache/LICENSE.txt new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/LICENSE.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java b/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java @@ -0,0 +1,81 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + *

Thrown when more than one option in an option group + * has been provided.

+ * + * @author John Keyes ( john at integralsource.com ) + * @see ParseException + */ +public class AlreadySelectedException extends ParseException { + + /** + *

Construct a new AlreadySelectedException + * with the specified detail message.

+ * + * @param message the detail message + */ + public AlreadySelectedException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/BasicParser.java b/installer/src/java/org/apache/commons/cli/BasicParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/BasicParser.java @@ -0,0 +1,92 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + * The class BasicParser provides a very simple implementation of + * the {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + */ +public class BasicParser extends Parser { + + /** + *

A simple implementation of {@link Parser}'s abstract + * {@link Parser#flatten(Options,String[],boolean) flatten} method.

+ * + *

Note: options and stopAtNonOption + * are not used in this flatten method.

+ * + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed + * @param stopAtNonOption Specifies whether to stop flattening + * when an non option is found. + * @return The arguments String array. + */ + protected String[] flatten( Options options, + String[] arguments, + boolean stopAtNonOption ) + { + // just echo the arguments + return arguments; + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/CommandLine.java b/installer/src/java/org/apache/commons/cli/CommandLine.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/CommandLine.java @@ -0,0 +1,328 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; + +/** + *

Represents list of arguments parsed against + * a {@link Options} descriptor.

+ * + *

It allows querying of a boolean {@link #hasOption(String opt)}, + * in addition to retrieving the {@link #getOptionValue(String opt)} + * for options requiring arguments.

+ * + *

Additionally, any left-over or unrecognized arguments, + * are available for further processing.

+ * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @author John Keyes (john at integralsource.com) + */ +public class CommandLine { + + /** the unrecognised options/arguments */ + private List args = new LinkedList(); + + /** the processed options */ + private Map options = new HashMap(); + + /** Map of unique options for ease to get complete list of options */ + private Map hashcodeMap = new HashMap(); + + /** the processed options */ + private Option[] optionsArray; + + /** + *

Creates a command line.

+ */ + CommandLine() { + } + + /** + *

Query to see if an option has been set.

+ * + * @param opt Short name of the option + * @return true if set, false if not + */ + public boolean hasOption(String opt) { + return options.containsKey( opt ); + } + + /** + *

Query to see if an option has been set.

+ * + * @param opt character name of the option + * @return true if set, false if not + */ + public boolean hasOption( char opt ) { + return hasOption( String.valueOf( opt ) ); + } + + /** + *

Return the Object type of this Option.

+ * + * @param opt the name of the option + * @return the type of this Option + */ + public Object getOptionObject( String opt ) { + String res = getOptionValue( opt ); + + Object type = ((Option)((List)options.get(opt)).iterator().next()).getType(); + return res == null ? null : TypeHandler.createValue(res, type); + } + + /** + *

Return the Object type of this Option.

+ * + * @param opt the name of the option + * @return the type of opt + */ + public Object getOptionObject( char opt ) { + return getOptionObject( String.valueOf( opt ) ); + } + + /** + *

Retrieve the argument, if any, of this option.

+ * + * @param opt the name of the option + * @return Value of the argument if option is set, and has an argument, + * otherwise null. + */ + public String getOptionValue( String opt ) { + String[] values = getOptionValues(opt); + return (values == null) ? null : values[0]; + } + + /** + *

Retrieve the argument, if any, of this option.

+ * + * @param opt the character name of the option + * @return Value of the argument if option is set, and has an argument, + * otherwise null. + */ + public String getOptionValue( char opt ) { + return getOptionValue( String.valueOf( opt ) ); + } + + /** + *

Retrieves the array of values, if any, of an option.

+ * + * @param opt string name of the option + * @return Values of the argument if option is set, and has an argument, + * otherwise null. + */ + public String[] getOptionValues( String opt ) { + List values = new java.util.ArrayList(); + + if( options.containsKey( opt ) ) { + List opts = (List)options.get( opt ); + Iterator iter = opts.iterator(); + + while( iter.hasNext() ) { + Option optt = (Option)iter.next(); + values.addAll( optt.getValuesList() ); + } + } + return (values.size() == 0) ? null : (String[])values.toArray(new String[]{}); + } + + /** + *

Retrieves the array of values, if any, of an option.

+ * + * @param opt character name of the option + * @return Values of the argument if option is set, and has an argument, + * otherwise null. + */ + public String[] getOptionValues( char opt ) { + return getOptionValues( String.valueOf( opt ) ); + } + + /** + *

Retrieve the argument, if any, of an option.

+ * + * @param opt name of the option + * @param defaultValue is the default value to be returned if the option is not specified + * @return Value of the argument if option is set, and has an argument, + * otherwise defaultValue. + */ + public String getOptionValue( String opt, String defaultValue ) { + String answer = getOptionValue( opt ); + return ( answer != null ) ? answer : defaultValue; + } + + /** + *

Retrieve the argument, if any, of an option.

+ * + * @param opt character name of the option + * @param defaultValue is the default value to be returned if the option is not specified + * @return Value of the argument if option is set, and has an argument, + * otherwise defaultValue. + */ + public String getOptionValue( char opt, String defaultValue ) { + return getOptionValue( String.valueOf( opt ), defaultValue ); + } + + /** + *

Retrieve any left-over non-recognized options and arguments

+ * + * @return remaining items passed in but not parsed as an array + */ + public String[] getArgs() { + String[] answer = new String[ args.size() ]; + args.toArray( answer ); + return answer; + } + + /** + *

Retrieve any left-over non-recognized options and arguments

+ * + * @return remaining items passed in but not parsed as a List. + */ + public List getArgList() { + return args; + } + + /** + * jkeyes + * - commented out until it is implemented properly + *

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + /* + public String toString() { + StringBuffer buf = new StringBuffer(); + + buf.append( "[ CommandLine: [ options: " ); + buf.append( options.toString() ); + buf.append( " ] [ args: "); + buf.append( args.toString() ); + buf.append( " ] ]" ); + + return buf.toString(); + } + */ + + /** + *

Add left-over unrecognized option/argument.

+ * + * @param arg the unrecognised option/argument. + */ + void addArg(String arg) { + args.add( arg ); + } + + /** + *

Add an option to the command line. The values of + * the option are stored.

+ * + * @param opt the processed option + */ + void addOption( Option opt ) { + hashcodeMap.put( new Integer( opt.hashCode() ), opt ); + + String key = opt.getOpt(); + if( " ".equals(key) ) { + key = opt.getLongOpt(); + } + + if( options.get( key ) != null ) { + ((java.util.List)options.get( key )).add( opt ); + } + else { + options.put( key, new java.util.ArrayList() ); + ((java.util.List)options.get( key ) ).add( opt ); + } + } + + /** + *

Returns an iterator over the Option members of CommandLine.

+ * + * @return an Iterator over the processed {@link Option} + * members of this {@link CommandLine} + */ + public Iterator iterator( ) { + return hashcodeMap.values().iterator(); + } + + /** + *

Returns an array of the processed {@link Option}s.

+ * + * @return an array of the processed {@link Option}s. + */ + public Option[] getOptions( ) { + Collection processed = hashcodeMap.values(); + + // reinitialise array + optionsArray = new Option[ processed.size() ]; + + // return the array + return (Option[]) processed.toArray( optionsArray ); + } + +} diff --git a/installer/src/java/org/apache/commons/cli/CommandLineParser.java b/installer/src/java/org/apache/commons/cli/CommandLineParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/CommandLineParser.java @@ -0,0 +1,97 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + * A class that implements the CommandLineParser interface + * can parse a String array according to the {@link Options} specified + * and return a {@link CommandLine}. + * + * @author John Keyes (john at integralsource.com) + */ +public interface CommandLineParser { + + /** + * Parse the arguments according to the specified options. + * + * @param options the specified Options + * @param arguments the command line arguments + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered + * while parsing the command line tokens. + */ + public CommandLine parse( Options options, String[] arguments ) + throws ParseException; + + /** + * Parse the arguments according to the specified options. + * + * @param options the specified Options + * @param arguments the command line arguments + * @param stopAtNonOption specifies whether to continue parsing the + * arguments if a non option is encountered. + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered + * while parsing the command line tokens. + */ + public CommandLine parse( Options options, String[] arguments, boolean stopAtNonOption ) + throws ParseException; +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/GnuParser.java b/installer/src/java/org/apache/commons/cli/GnuParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/GnuParser.java @@ -0,0 +1,187 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * The class GnuParser provides an implementation of the + * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2662 $ + */ +public class GnuParser extends Parser { + + /** holder for flattened tokens */ + private ArrayList tokens = new ArrayList(); + + /** + *

Resets the members to their original state i.e. remove + * all of tokens entries. + */ + private void init() { + tokens.clear(); + } + + /** + *

This flatten method does so using the following rules: + *

    + *
  1. If an {@link Option} exists for the first character of + * the arguments entry AND an {@link Option} + * does not exist for the whole argument then + * add the first character as an option to the processed tokens + * list e.g. "-D" and add the rest of the entry to the also.
  2. + *
  3. Otherwise just add the token to the processed tokens list. + *
  4. + *
+ *

+ */ + protected String[] flatten( Options options, + String[] arguments, + boolean stopAtNonOption ) + { + init(); + boolean eatTheRest = false; + Option currentOption = null; + + for( int i = 0; i < arguments.length; i++ ) { + if( "--".equals( arguments[i] ) ) { + eatTheRest = true; + tokens.add( "--" ); + } + else if ( "-".equals( arguments[i] ) ) { + tokens.add( "-" ); + } + else if( arguments[i].startsWith( "-" ) ) { + Option option = options.getOption( arguments[i] ); + + // this is not an Option + if( option == null ) { + // handle special properties Option + Option specialOption = options.getOption( arguments[i].substring(0,2) ); + if( specialOption != null ) { + tokens.add( arguments[i].substring(0,2) ); + tokens.add( arguments[i].substring(2) ); + } + else if( stopAtNonOption ) { + eatTheRest = true; + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + else { + currentOption = option; + // special option + Option specialOption = options.getOption( arguments[i].substring(0,2) ); + if( specialOption != null && option == null ) { + tokens.add( arguments[i].substring(0,2) ); + tokens.add( arguments[i].substring(2) ); + } + else if( currentOption != null && currentOption.hasArg() ) { + if( currentOption.hasArg() ) { + tokens.add( arguments[i] ); + currentOption= null; + } + else if ( currentOption.hasArgs() ) { + tokens.add( arguments[i] ); + } + else if ( stopAtNonOption ) { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + else if (currentOption != null ) { + tokens.add( arguments[i] ); + } + else if ( stopAtNonOption ) { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + } + else { + tokens.add( arguments[i] ); + } + + if( eatTheRest ) { + for( i++; i < arguments.length; i++ ) { + tokens.add( arguments[i] ); + } + } + } + return (String[])tokens.toArray( new String[] {} ); + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/HelpFormatter.java b/installer/src/java/org/apache/commons/cli/HelpFormatter.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/HelpFormatter.java @@ -0,0 +1,542 @@ +/* + * $Header$ + * $Revision: 2690 $ + * $Date: 2006-03-25 23:39:57 -0800 (Sat, 25 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * A formatter of help messages for the current command line options + * + * @author Slawek Zachcial + * @author John Keyes (john at integralsource.com) + **/ +public class HelpFormatter +{ + // --------------------------------------------------------------- Constants + + public static final int DEFAULT_WIDTH = 74; + public static final int DEFAULT_LEFT_PAD = 1; + public static final int DEFAULT_DESC_PAD = 3; + public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; + public static final String DEFAULT_OPT_PREFIX = "-"; + public static final String DEFAULT_LONG_OPT_PREFIX = "--"; + public static final String DEFAULT_ARG_NAME = "arg"; + + // ------------------------------------------------------------------ Static + + // -------------------------------------------------------------- Attributes + + public int defaultWidth; + public int defaultLeftPad; + public int defaultDescPad; + public String defaultSyntaxPrefix; + public String defaultNewLine; + public String defaultOptPrefix; + public String defaultLongOptPrefix; + public String defaultArgName; + + // ------------------------------------------------------------ Constructors + public HelpFormatter() + { + defaultWidth = DEFAULT_WIDTH; + defaultLeftPad = DEFAULT_LEFT_PAD; + defaultDescPad = DEFAULT_DESC_PAD; + defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX; + defaultNewLine = System.getProperty("line.separator"); + defaultOptPrefix = DEFAULT_OPT_PREFIX; + defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; + defaultArgName = DEFAULT_ARG_NAME; + } + + // ------------------------------------------------------------------ Public + + public void printHelp( String cmdLineSyntax, + Options options ) + { + printHelp( defaultWidth, cmdLineSyntax, null, options, null, false ); + } + + public void printHelp( String cmdLineSyntax, + Options options, + boolean autoUsage ) + { + printHelp( defaultWidth, cmdLineSyntax, null, options, null, autoUsage ); + } + + public void printHelp( String cmdLineSyntax, + String header, + Options options, + String footer ) + { + printHelp( cmdLineSyntax, header, options, footer, false ); + } + + public void printHelp( String cmdLineSyntax, + String header, + Options options, + String footer, + boolean autoUsage ) + { + printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage ); + } + + public void printHelp( int width, + String cmdLineSyntax, + String header, + Options options, + String footer ) + { + printHelp( width, cmdLineSyntax, header, options, footer, false ); + } + + public void printHelp( int width, + String cmdLineSyntax, + String header, + Options options, + String footer, + boolean autoUsage ) + { + PrintWriter pw = new PrintWriter(System.out); + printHelp( pw, width, cmdLineSyntax, header, + options, defaultLeftPad, defaultDescPad, footer, autoUsage ); + pw.flush(); + } + public void printHelp( PrintWriter pw, + int width, + String cmdLineSyntax, + String header, + Options options, + int leftPad, + int descPad, + String footer ) + throws IllegalArgumentException + { + printHelp( pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false ); + } + + public void printHelp( PrintWriter pw, + int width, + String cmdLineSyntax, + String header, + Options options, + int leftPad, + int descPad, + String footer, + boolean autoUsage ) + throws IllegalArgumentException + { + if ( cmdLineSyntax == null || cmdLineSyntax.length() == 0 ) + { + throw new IllegalArgumentException("cmdLineSyntax not provided"); + } + + if ( autoUsage ) { + printUsage( pw, width, cmdLineSyntax, options ); + } + else { + printUsage( pw, width, cmdLineSyntax ); + } + + if ( header != null && header.trim().length() > 0 ) + { + printWrapped( pw, width, header ); + } + printOptions( pw, width, options, leftPad, descPad ); + if ( footer != null && footer.trim().length() > 0 ) + { + printWrapped( pw, width, footer ); + } + } + + /** + *

Prints the usage statement for the specified application.

+ * + * @param pw The PrintWriter to print the usage statement + * @param width ?? + * @param appName The application name + * @param options The command line Options + * + */ + public void printUsage( PrintWriter pw, int width, String app, Options options ) + { + // initialise the string buffer + StringBuffer buff = new StringBuffer( defaultSyntaxPrefix ).append( app ).append( " " ); + + // create a list for processed option groups + ArrayList list = new ArrayList(); + + // temp variable + Option option; + + // iterate over the options + for ( Iterator i = options.getOptions().iterator(); i.hasNext(); ) + { + // get the next Option + option = (Option) i.next(); + + // check if the option is part of an OptionGroup + OptionGroup group = options.getOptionGroup( option ); + + // if the option is part of a group + if( group != null ) { + // if the group has not already been processed + if(!list.contains(group)) { + + // add the group to the processed list + list.add( group ); + + // get the names of the options from the OptionGroup + Collection names = group.getNames(); + + buff.append( "[" ); + + // for each option in the OptionGroup + for( Iterator iter = names.iterator(); iter.hasNext(); ) { + buff.append( iter.next() ); + if( iter.hasNext() ) { + buff.append( " | " ); + } + } + buff.append( "] " ); + } + } else { // if the Option is not part of an OptionGroup + // if the Option is not a required option + if( !option.isRequired() ) { + buff.append( "[" ); + } + + if( !" ".equals( option.getOpt() ) ) { + buff.append( "-" ).append( option.getOpt() ); + } + else { + buff.append( "--" ).append( option.getLongOpt() ); + } + + if( option.hasArg() ){ + buff.append( " " ); + } + + // if the Option has a value + if( option.hasArg() ) { + buff.append( option.getArgName() ); + } + + // if the Option is not a required option + if( !option.isRequired() ) { + buff.append( "]" ); + } + buff.append( " " ); + } + } + + // call printWrapped + printWrapped( pw, width, buff.toString().indexOf(' ')+1, + buff.toString() ); + } + + public void printUsage( PrintWriter pw, int width, String cmdLineSyntax ) + { + int argPos = cmdLineSyntax.indexOf(' ') + 1; + printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, + defaultSyntaxPrefix + cmdLineSyntax); + } + + public void printOptions( PrintWriter pw, int width, Options options, int leftPad, int descPad ) + { + StringBuffer sb = new StringBuffer(); + renderOptions(sb, width, options, leftPad, descPad); + pw.println(sb.toString()); + } + + public void printWrapped( PrintWriter pw, int width, String text ) + { + printWrapped(pw, width, 0, text); + } + + public void printWrapped( PrintWriter pw, int width, int nextLineTabStop, String text ) + { + StringBuffer sb = new StringBuffer(text.length()); + renderWrappedText(sb, width, nextLineTabStop, text); + pw.println(sb.toString()); + } + + // --------------------------------------------------------------- Protected + + protected StringBuffer renderOptions( StringBuffer sb, + int width, + Options options, + int leftPad, + int descPad ) + { + final String lpad = createPadding(leftPad); + final String dpad = createPadding(descPad); + + //first create list containing only -a,--aaa where -a is opt and --aaa is + //long opt; in parallel look for the longest opt string + //this list will be then used to sort options ascending + int max = 0; + StringBuffer optBuf; + List prefixList = new ArrayList(); + Option option; + List optList = options.helpOptions(); + if (!options.isSortAsAdded()) { + Collections.sort(optList, new StringBufferComparator()); + } + for ( Iterator i = optList.iterator(); i.hasNext(); ) + { + option = (Option) i.next(); + optBuf = new StringBuffer(8); + + if (option.getOpt().equals(" ")) + { + optBuf.append(lpad).append(" " + defaultLongOptPrefix).append(option.getLongOpt()); + } + else + { + optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt()); + if ( option.hasLongOpt() ) + { + optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt()); + } + + } + + if( option.hasArg() ) { + if( option.hasArgName() ) { + optBuf.append(" <").append( option.getArgName() ).append( '>' ); + } + else { + optBuf.append(' '); + } + } + + prefixList.add(optBuf); + max = optBuf.length() > max ? optBuf.length() : max; + } + int x = 0; + for ( Iterator i = optList.iterator(); i.hasNext(); ) + { + option = (Option) i.next(); + optBuf = new StringBuffer( prefixList.get( x++ ).toString() ); + + if ( optBuf.length() < max ) + { + optBuf.append(createPadding(max - optBuf.length())); + } + optBuf.append( dpad ); + + int nextLineTabStop = max + descPad; + renderWrappedText(sb, width, nextLineTabStop, + optBuf.append(option.getDescription()).toString()); + if ( i.hasNext() ) + { + sb.append(defaultNewLine); + } + } + + return sb; + } + + protected StringBuffer renderWrappedText( StringBuffer sb, + int width, + int nextLineTabStop, + String text ) + { + int pos = findWrapPos( text, width, 0); + if ( pos == -1 ) + { + sb.append(rtrim(text)); + return sb; + } + else + { + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + //all following lines must be padded with nextLineTabStop space characters + final String padding = createPadding(nextLineTabStop); + + while ( true ) + { + text = padding + text.substring(pos).trim(); + pos = findWrapPos( text, width, nextLineTabStop ); + if ( pos == -1 ) + { + sb.append(text); + return sb; + } + + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + } + + /** + * Finds the next text wrap position after startPos for the text + * in sb with the column width width. + * The wrap point is the last postion before startPos+width having a whitespace + * character (space, \n, \r). + * + * @param sb text to be analyzed + * @param width width of the wrapped text + * @param startPos position from which to start the lookup whitespace character + * @return postion on which the text must be wrapped or -1 if the wrap position is at the end + * of the text + */ + protected int findWrapPos( String text, int width, int startPos ) + { + int pos = -1; + // the line ends before the max wrap pos or a new line char found + if ( ((pos = text.indexOf('\n', startPos)) != -1 && pos <= width) || + ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width) ) + { + return pos; + } + else if ( (startPos + width) >= text.length() ) + { + return -1; + } + + //look for the last whitespace character before startPos+width + pos = startPos + width; + char c; + while ( pos >= startPos && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) + { + --pos; + } + //if we found it - just return + if ( pos > startPos ) + { + return pos; + } + else + { + //must look for the first whitespace chearacter after startPos + width + pos = startPos + width; + while ( pos <= text.length() && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) + { + ++pos; + } + return pos == text.length() ? -1 : pos; + } + } + + protected String createPadding(int len) + { + StringBuffer sb = new StringBuffer(len); + for ( int i = 0; i < len; ++i ) + { + sb.append(' '); + } + return sb.toString(); + } + + protected String rtrim( String s ) + { + if ( s == null || s.length() == 0 ) + { + return s; + } + + int pos = s.length(); + while ( pos >= 0 && Character.isWhitespace(s.charAt(pos-1)) ) + { + --pos; + } + return s.substring(0, pos); + } + + // ------------------------------------------------------- Package protected + + // ----------------------------------------------------------------- Private + + // ----------------------------------------------------------- Inner classes + + private static class StringBufferComparator + implements Comparator + { + public int compare( Object o1, Object o2 ) + { + String str1 = stripPrefix(o1.toString()); + String str2 = stripPrefix(o2.toString()); + return (str1.compareTo(str2)); + } + + private String stripPrefix(String strOption) + { + // Strip any leading '-' characters + int iStartIndex = strOption.lastIndexOf('-'); + if (iStartIndex == -1) + { + iStartIndex = 0; + } + return strOption.substring(iStartIndex); + + } + } +} diff --git a/installer/src/java/org/apache/commons/cli/MissingArgumentException.java b/installer/src/java/org/apache/commons/cli/MissingArgumentException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/MissingArgumentException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Thrown when an option requiring an argument + * is not provided with an argument.

+ * + * @author John Keyes (john at integralsource.com) + * @see ParseException + */ +public class MissingArgumentException extends ParseException { + + /** + *

Construct a new MissingArgumentException + * with the specified detail message.

+ * + * @param message the detail message + */ + public MissingArgumentException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/MissingOptionException.java b/installer/src/java/org/apache/commons/cli/MissingOptionException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/MissingOptionException.java @@ -0,0 +1,81 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Thrown when a required option has not been provided.

+ * + * @author John Keyes ( john at integralsource.com ) + * @see ParseException + */ +public class MissingOptionException extends ParseException { + + /** + *

Construct a new MissingSelectedException + * with the specified detail message.

+ * + * @param message the detail message + */ + public MissingOptionException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Option.java b/installer/src/java/org/apache/commons/cli/Option.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Option.java @@ -0,0 +1,575 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: Option.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; + +/**

Describes a single command-line option. It maintains + * information regarding the short-name of the option, the long-name, + * if any exists, a flag indicating if an argument is required for + * this option, and a self-documenting description of the option.

+ * + *

An Option is not created independantly, but is create through + * an instance of {@link Options}.

+ * + * @see org.apache.commons.cli.Options + * @see org.apache.commons.cli.CommandLine + * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @version $Revision: 2662 $ + */ + +public class Option implements Cloneable { + + /** constant that specifies the number of argument values has not been specified */ + public final static int UNINITIALIZED = -1; + + /** constant that specifies the number of argument values is infinite */ + public final static int UNLIMITED_VALUES = -2; + + /** opt the single character representation of the option */ + private String opt; + + /** longOpt is the long representation of the option */ + private String longOpt; + + /** hasArg specifies whether this option has an associated argument */ + private boolean hasArg; + + /** argName specifies the name of the argument for this option */ + private String argName; + + /** description of the option */ + private String description; + + /** required specifies whether this option is required to be present */ + private boolean required; + + /** specifies whether the argument value of this Option is optional */ + private boolean optionalArg; + + /** + * numberOfArgs specifies the number of argument values this option + * can have + */ + private int numberOfArgs = UNINITIALIZED; + + /** the type of this Option */ + private Object type; + + /** the list of argument values **/ + private ArrayList values = new ArrayList(); + + /** option char (only valid for single character options) */ + private char id; + + /** the character that is the value separator */ + private char valuesep; + + /** + *

Validates whether opt is a permissable Option + * shortOpt. The rules that specify if the opt + * is valid are:

+ *
    + *
  • opt is not NULL
  • + *
  • a single character opt that is either + * ' '(special case), '?', '@' or a letter
  • + *
  • a multi character opt that only contains + * letters.
  • + *
+ * + * @param opt The option string to validate + * @throws IllegalArgumentException if the Option is not valid. + */ + private void validateOption( String opt ) + throws IllegalArgumentException + { + // check that opt is not NULL + if( opt == null ) { + throw new IllegalArgumentException( "opt is null" ); + } + // handle the single character opt + else if( opt.length() == 1 ) { + char ch = opt.charAt( 0 ); + if ( !isValidOpt( ch ) ) { + throw new IllegalArgumentException( "illegal option value '" + + ch + "'" ); + } + id = ch; + } + // handle the multi character opt + else { + char[] chars = opt.toCharArray(); + for( int i = 0; i < chars.length; i++ ) { + if( !isValidChar( chars[i] ) ) { + throw new IllegalArgumentException( "opt contains illegal character value '" + chars[i] + "'" ); + } + } + } + } + + /** + *

Returns whether the specified character is a valid Option.

+ * + * @param c the option to validate + * @return true if c is a letter, ' ', '?' or '@', otherwise false. + */ + private boolean isValidOpt( char c ) { + return ( isValidChar( c ) || c == ' ' || c == '?' || c == '@' ); + } + + /** + *

Returns whether the specified character is a valid character.

+ * + * @param c the character to validate + * @return true if c is a letter. + */ + private boolean isValidChar( char c ) { + return Character.isJavaIdentifierPart( c ); + } + + /** + *

Returns the id of this Option. This is only set when the + * Option shortOpt is a single character. This is used for switch + * statements.

+ * + * @return the id of this Option + */ + public int getId( ) { + return id; + } + + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, String description ) + throws IllegalArgumentException + { + this( opt, null, false, description ); + } + + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, boolean hasArg, String description ) + throws IllegalArgumentException + { + this( opt, null, hasArg, description ); + } + + /** + *

Creates an Option using the specified parameters.

+ * + * @param opt short representation of the option + * @param longOpt the long representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, String longOpt, boolean hasArg, String description ) + throws IllegalArgumentException + { + // ensure that the option is valid + validateOption( opt ); + + this.opt = opt; + this.longOpt = longOpt; + + // if hasArg is set then the number of arguments is 1 + if( hasArg ) { + this.numberOfArgs = 1; + } + + this.hasArg = hasArg; + this.description = description; + } + + /**

Retrieve the name of this Option.

+ * + *

It is this String which can be used with + * {@link CommandLine#hasOption(String opt)} and + * {@link CommandLine#getOptionValue(String opt)} to check + * for existence and argument.

+ * + * @return The name of this option + */ + public String getOpt() { + return this.opt; + } + + /** + *

Retrieve the type of this Option.

+ * + * @return The type of this option + */ + public Object getType() { + return this.type; + } + + /** + *

Sets the type of this Option.

+ * + * @param type the type of this Option + */ + public void setType( Object type ) { + this.type = type; + } + + /** + *

Retrieve the long name of this Option.

+ * + * @return Long name of this option, or null, if there is no long name + */ + public String getLongOpt() { + return this.longOpt; + } + + /** + *

Sets the long name of this Option.

+ * + * @param longOpt the long name of this Option + */ + public void setLongOpt( String longOpt ) { + this.longOpt = longOpt; + } + + /** + *

Sets whether this Option can have an optional argument.

+ * + * @param optionalArg specifies whether the Option can have + * an optional argument. + */ + public void setOptionalArg( boolean optionalArg ) { + this.optionalArg = optionalArg; + } + + /** + * @return whether this Option can have an optional argument + */ + public boolean hasOptionalArg( ) { + return this.optionalArg; + } + + /**

Query to see if this Option has a long name

+ * + * @return boolean flag indicating existence of a long name + */ + public boolean hasLongOpt() { + return ( this.longOpt != null ); + } + + /**

Query to see if this Option requires an argument

+ * + * @return boolean flag indicating if an argument is required + */ + public boolean hasArg() { + return this.numberOfArgs > 0 || numberOfArgs == UNLIMITED_VALUES; + } + + /**

Retrieve the self-documenting description of this Option

+ * + * @return The string description of this option + */ + public String getDescription() { + return this.description; + } + + /** + *

Query to see if this Option requires an argument

+ * + * @return boolean flag indicating if an argument is required + */ + public boolean isRequired() { + return this.required; + } + + /** + *

Sets whether this Option is mandatory.

+ * + * @param required specifies whether this Option is mandatory + */ + public void setRequired( boolean required ) { + this.required = required; + } + + /** + *

Sets the display name for the argument value.

+ * + * @param argName the display name for the argument value. + */ + public void setArgName( String argName ) { + this.argName = argName; + } + + /** + *

Gets the display name for the argument value.

+ * + * @return the display name for the argument value. + */ + public String getArgName() { + return this.argName; + } + + /** + *

Returns whether the display name for the argument value + * has been set.

+ * + * @return if the display name for the argument value has been + * set. + */ + public boolean hasArgName() { + return (this.argName != null && this.argName.length() > 0 ); + } + + /** + *

Query to see if this Option can take many values

+ * + * @return boolean flag indicating if multiple values are allowed + */ + public boolean hasArgs() { + return ( this.numberOfArgs > 1 || this.numberOfArgs == UNLIMITED_VALUES ); + } + + /** + *

Sets the number of argument values this Option can take.

+ * + * @param num the number of argument values + */ + public void setArgs( int num ) { + this.numberOfArgs = num; + } + + /** + *

Sets the value separator. For example if the argument value + * was a Java property, the value separator would be '='.

+ * + * @param sep The value separator. + */ + public void setValueSeparator( char sep ) { + this.valuesep = sep; + } + + /** + *

Returns the value separator character.

+ * + * @return the value separator character. + */ + public char getValueSeparator() { + return this.valuesep; + } + + /** + *

Returns the number of argument values this Option can take.

+ * + * @return num the number of argument values + */ + public int getArgs( ) { + return this.numberOfArgs; + } + + /** + *

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + public String toString() { + StringBuffer buf = new StringBuffer().append("[ option: "); + + buf.append( this.opt ); + + if ( this.longOpt != null ) { + buf.append(" ") + .append(this.longOpt); + } + + buf.append(" "); + + if ( hasArg ) { + buf.append( "+ARG" ); + } + + buf.append(" :: ") + .append( this.description ); + + if ( this.type != null ) { + buf.append(" :: ") + .append( this.type ); + } + + buf.append(" ]"); + return buf.toString(); + } + + /** + *

Adds the specified value to this Option.

+ * + * @param value is a/the value of this Option + */ + public boolean addValue( String value ) { + + switch( numberOfArgs ) { + case UNINITIALIZED: + return false; + case UNLIMITED_VALUES: + if( getValueSeparator() > 0 ) { + int index = 0; + while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { + this.values.add( value.substring( 0, index ) ); + value = value.substring( index+1 ); + } + } + this.values.add( value ); + return true; + default: + if( getValueSeparator() > 0 ) { + int index = 0; + while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { + if( values.size() > numberOfArgs-1 ) { + return false; + } + this.values.add( value.substring( 0, index ) ); + value = value.substring( index+1 ); + } + } + if( values.size() > numberOfArgs-1 ) { + return false; + } + this.values.add( value ); + return true; + } + } + + /** + * @return the value/first value of this Option or + * null if there are no values. + */ + public String getValue() { + return this.values.size()==0 ? null : (String)this.values.get( 0 ); + } + + /** + * @return the specified value of this Option or + * null if there are no values. + */ + public String getValue( int index ) + throws IndexOutOfBoundsException + { + return ( this.values.size()==0 ) ? null : (String)this.values.get( index ); + } + + /** + * @return the value/first value of this Option or the + * defaultValue if there are no values. + */ + public String getValue( String defaultValue ) { + String value = getValue( ); + return ( value != null ) ? value : defaultValue; + } + + /** + * @return the values of this Option as a String array + * or null if there are no values + */ + public String[] getValues() { + return this.values.size()==0 ? null : (String[])this.values.toArray(new String[]{}); + } + + /** + * @return the values of this Option as a List + * or null if there are no values + */ + public java.util.List getValuesList() { + return this.values; + } + + /** + * @return a copy of this Option + */ + public Object clone() { + Option option = new Option( getOpt(), getDescription() ); + option.setArgs( getArgs() ); + option.setOptionalArg( hasOptionalArg() ); + option.setRequired( isRequired() ); + option.setLongOpt( getLongOpt() ); + option.setType( getType() ); + option.setValueSeparator( getValueSeparator() ); + return option; + } +} diff --git a/installer/src/java/org/apache/commons/cli/OptionBuilder.java b/installer/src/java/org/apache/commons/cli/OptionBuilder.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/OptionBuilder.java @@ -0,0 +1,368 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

OptionBuilder allows the user to create Options using descriptive + * methods.

+ *

Details on the Builder pattern can be found at + * http://c2.com/cgi-bin/wiki?BuilderPattern.

+ * + * @author John Keyes ( john at integralsource.com ) + * @since 1.0 + */ +public class OptionBuilder { + + /** long option */ + private static String longopt; + /** option description */ + private static String description; + /** argument name */ + private static String argName; + /** is required? */ + private static boolean required; + /** the number of arguments */ + private static int numberOfArgs = Option.UNINITIALIZED; + /** option type */ + private static Object type; + /** option can have an optional argument value */ + private static boolean optionalArg; + /** value separator for argument value */ + private static char valuesep; + + /** option builder instance */ + private static OptionBuilder instance = new OptionBuilder(); + + // private constructor + private OptionBuilder() { + } + + /** + *

Resets the member variables to their default values.

+ */ + private static void reset() { + description = null; + argName = null; + longopt = null; + type = null; + required = false; + numberOfArgs = Option.UNINITIALIZED; + + // PMM 9/6/02 - these were missing + optionalArg = false; + valuesep = (char) 0; + } + + /** + *

The next Option created will have the following long option value.

+ * + * @param longopt the long option value + * @return the OptionBuilder instance + */ + public static OptionBuilder withLongOpt( String longopt ) { + instance.longopt = longopt; + return instance; + } + + /** + *

The next Option created will require an argument value.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArg( ) { + instance.numberOfArgs = 1; + return instance; + } + + /** + *

The next Option created will require an argument value if + * hasArg is true.

+ * + * @param hasArg if true then the Option has an argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArg( boolean hasArg ) { + instance.numberOfArgs = ( hasArg == true ) ? 1 : Option.UNINITIALIZED; + return instance; + } + + /** + *

The next Option created will have the specified argument value + * name.

+ * + * @param name the name for the argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder withArgName( String name ) { + instance.argName = name; + return instance; + } + + /** + *

The next Option created will be required.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder isRequired( ) { + instance.required = true; + return instance; + } + + /** + *

The next Option created uses sep as a means to + * separate argument values.

+ * + * Example: + *
+     * Option opt = OptionBuilder.withValueSeparator( ':' )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * 
+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder withValueSeparator( char sep ) { + instance.valuesep = sep; + return instance; + } + + /** + *

The next Option created uses '=' as a means to + * separate argument values.

+ * + * Example: + *
+     * Option opt = OptionBuilder.withValueSeparator( )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * 
+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder withValueSeparator( ) { + instance.valuesep = '='; + return instance; + } + + /** + *

The next Option created will be required if required + * is true.

+ * + * @param required if true then the Option is required + * @return the OptionBuilder instance + */ + public static OptionBuilder isRequired( boolean required ) { + instance.required = required; + return instance; + } + + /** + *

The next Option created can have unlimited argument values.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArgs( ) { + instance.numberOfArgs = Option.UNLIMITED_VALUES; + return instance; + } + + /** + *

The next Option created can have num + * argument values.

+ * + * @param num the number of args that the option can have + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArgs( int num ) { + instance.numberOfArgs = num; + return instance; + } + + /** + *

The next Option can have an optional argument.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArg( ) { + instance.numberOfArgs = 1; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option can have an unlimited number of + * optional arguments.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArgs( ) { + instance.numberOfArgs = Option.UNLIMITED_VALUES; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option can have the specified number of + * optional arguments.

+ * + * @param numArgs - the maximum number of optional arguments + * the next Option created can have. + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArgs( int numArgs ) { + instance.numberOfArgs = numArgs; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option created will have a value that will be an instance + * of type.

+ * + * @param type the type of the Options argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder withType( Object type ) { + instance.type = type; + return instance; + } + + /** + *

The next Option created will have the specified description

+ * + * @param description a description of the Option's purpose + * @return the OptionBuilder instance + */ + public static OptionBuilder withDescription( String description ) { + instance.description = description; + return instance; + } + + /** + *

Create an Option using the current settings and with + * the specified Option char.

+ * + * @param opt the character representation of the Option + * @return the Option instance + * @throws IllegalArgumentException if opt is not + * a valid character. See Option. + */ + public static Option create( char opt ) + throws IllegalArgumentException + { + return create( String.valueOf( opt ) ); + } + + /** + *

Create an Option using the current settings

+ * + * @return the Option instance + * @throws IllegalArgumentException if longOpt has + * not been set. + */ + public static Option create() + throws IllegalArgumentException + { + if( longopt == null ) { + throw new IllegalArgumentException( "must specify longopt" ); + } + + return create( " " ); + } + + /** + *

Create an Option using the current settings and with + * the specified Option char.

+ * + * @param opt the java.lang.String representation + * of the Option + * @return the Option instance + * @throws IllegalArgumentException if opt is not + * a valid character. See Option. + */ + public static Option create( String opt ) + throws IllegalArgumentException + { + // create the option + Option option = new Option( opt, description ); + + // set the option properties + option.setLongOpt( longopt ); + option.setRequired( required ); + option.setOptionalArg( optionalArg ); + option.setArgs( numberOfArgs ); + option.setType( type ); + option.setValueSeparator( valuesep ); + option.setArgName( argName ); + // reset the OptionBuilder properties + instance.reset(); + + // return the Option instance + return option; + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/OptionGroup.java b/installer/src/java/org/apache/commons/cli/OptionGroup.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/OptionGroup.java @@ -0,0 +1,187 @@ +/* + * $Header$ + * $Revision: 2690 $ + * $Date: 2006-03-25 23:39:57 -0800 (Sat, 25 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * A group of mutually exclusive options. + * @author John Keyes ( john at integralsource.com ) + * @version $Revision: 2690 $ + */ +public class OptionGroup { + + /** hold the options in added order */ + private List optionList = new ArrayList(); + + /** the name of the selected option */ + private String selected; + + /** specified whether this group is required */ + private boolean required; + + /** + * add opt to this group + * + * @param opt the option to add to this group + * @return this option group with opt added + */ + public OptionGroup addOption(Option opt) { + optionList.add(opt); + return this; + } + + /** + * @return the names of the options in this group as a + * Collection + */ + public Collection getNames() { + List namesList = new ArrayList(); + Iterator optionsIterator = optionList.iterator(); + while (optionsIterator.hasNext()) { + Option option = (Option) optionsIterator.next(); + namesList.add("-" + option.getOpt()); + } + return namesList; + } + + /** + * @return the options in this group as a Collection + */ + public Collection getOptions() { + return optionList; + } + + /** + * set the selected option of this group to name. + * @param opt the option that is selected + * @throws AlreadySelectedException if an option from this group has + * already been selected. + */ + public void setSelected(Option opt) throws AlreadySelectedException { + // if no option has already been selected or the + // same option is being reselected then set the + // selected member variable + + if ( this.selected == null || this.selected.equals( opt.getOpt() ) ) { + this.selected = opt.getOpt(); + } + else { + throw new AlreadySelectedException( "an option from this group has " + + "already been selected: '" + + selected + "'"); + } + } + + /** + * @return the selected option name + */ + public String getSelected() { + return selected; + } + + /** + * @param required specifies if this group is required + */ + public void setRequired( boolean required ) { + this.required = required; + } + + /** + * Returns whether this option group is required. + * + * @returns whether this option group is required + */ + public boolean isRequired() { + return this.required; + } + + /** + *

Returns the stringified version of this OptionGroup.

+ * @return the stringified representation of this group + */ + public String toString() { + StringBuffer buff = new StringBuffer(); + + Iterator iter = getOptions().iterator(); + + buff.append( "[" ); + while( iter.hasNext() ) { + Option option = (Option)iter.next(); + + buff.append( "-" ); + buff.append( option.getOpt() ); + buff.append( " " ); + buff.append( option.getDescription( ) ); + + if( iter.hasNext() ) { + buff.append( ", " ); + } + } + buff.append( "]" ); + + return buff.toString(); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Options.java b/installer/src/java/org/apache/commons/cli/Options.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Options.java @@ -0,0 +1,331 @@ +/* + * $Header$ + * $Revision: 2694 $ + * $Date: 2006-03-27 11:45:13 -0800 (Mon, 27 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/**

Main entry-point into the library.

+ * + *

Options represents a collection of {@link Option} objects, which + * describe the possible options for a command-line.

+ * + *

It may flexibly parse long and short options, with or without + * values. Additionally, it may parse only a portion of a commandline, + * allowing for flexible multi-stage parsing.

+ * + * @see org.apache.commons.cli.CommandLine + * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @version $Revision: 2694 $ + */ +public class Options { + + /** a map of the options with the character key */ + private Map shortOpts = new HashMap(); + + /** a map of the options with the long key */ + private Map longOpts = new HashMap(); + + /** a list of the required options */ + private List requiredOpts = new ArrayList(); + + /** a map of the option groups */ + private Map optionGroups = new HashMap(); + + /** a list of all options, in sequence of addition, only in use when isSortAsAdded() */ + private List _addedOpts = new ArrayList(); + + /** flag for the sort behaviour */ + private boolean _sortAsAdded; + + /** + * Construct a new Options descriptor + */ + public Options() { + setSortAsAdded(false); + } + + /** + * Set the sort sequence: true means in sequence of addition, false will use the + * default (undefined) sorting + */ + public void setSortAsAdded(boolean sortAsAdded) { + _sortAsAdded = sortAsAdded; + } + + /** + *

Add the specified option group.

+ * + * @param group the OptionGroup that is to be added + * @return the resulting Options instance + */ + public Options addOptionGroup( OptionGroup group ) { + Iterator options = group.getOptions().iterator(); + + if( group.isRequired() ) { + requiredOpts.add( group ); + } + + while( options.hasNext() ) { + Option option = (Option)options.next(); + // an Option cannot be required if it is in an + // OptionGroup, either the group is required or + // nothing is required + option.setRequired( false ); + addOption( option ); + + optionGroups.put( option.getOpt(), group ); + } + + return this; + } + + /**

Add an option that only contains a short-name

+ *

It may be specified as requiring an argument.

+ * + * @param opt Short single-character name of the option. + * @param hasArg flag signally if an argument is required after this option + * @param description Self-documenting description + * @return the resulting Options instance + */ + public Options addOption(String opt, boolean hasArg, String description) { + addOption( opt, null, hasArg, description ); + return this; + } + + /**

Add an option that contains a short-name and a long-name

+ *

It may be specified as requiring an argument.

+ * + * @param opt Short single-character name of the option. + * @param longOpt Long multi-character name of the option. + * @param hasArg flag signally if an argument is required after this option + * @param description Self-documenting description + * @return the resulting Options instance + */ + public Options addOption(String opt, String longOpt, boolean hasArg, String description) { + addOption( new Option( opt, longOpt, hasArg, description ) ); + return this; + } + + /** + *

Adds an option instance

+ * + * @param opt the option that is to be added + * @return the resulting Options instance + */ + public Options addOption(Option opt) { + String shortOpt = "-" + opt.getOpt(); + + // add it to the long option list + if ( opt.hasLongOpt() ) { + longOpts.put( "--" + opt.getLongOpt(), opt ); + } + + // if the option is required add it to the required list + if ( opt.isRequired() ) { + requiredOpts.add( shortOpt ); + } + + shortOpts.put( shortOpt, opt ); + + if (isSortAsAdded()) { + _addedOpts.add(opt); + } + + return this; + } + + /**

Retrieve a read-only list of options in this set

+ * + * @return read-only Collection of {@link Option} objects in this descriptor + */ + public Collection getOptions() { + if (isSortAsAdded()) { + return _addedOpts; + } else { + List opts = new ArrayList(shortOpts.values()); + + // now look through the long opts to see if there are any Long-opt + // only options + Iterator iter = longOpts.values().iterator(); + while (iter.hasNext()) { + Object item = iter.next(); + if (!opts.contains(item)) { + opts.add(item); + } + } + return Collections.unmodifiableCollection(opts); + } + } + + /** + * @return true if options should be sorted in sequence of addition. + */ + public boolean isSortAsAdded() { + return _sortAsAdded; + } + + /** + *

Returns the Options for use by the HelpFormatter.

+ * + * @return the List of Options + */ + List helpOptions() { + if (isSortAsAdded()) { + return _addedOpts; + } else { + return new ArrayList(shortOpts.values()); + } + } + + /**

Returns the required options as a + * java.util.Collection.

+ * + * @return Collection of required options + */ + public List getRequiredOptions() { + return requiredOpts; + } + + /**

Retrieve the named {@link Option}

+ * + * @param opt short or long name of the {@link Option} + * @return the option represented by opt + */ + public Option getOption( String opt ) { + + Option option = null; + + // short option + if( opt.length() == 1 ) { + option = (Option)shortOpts.get( "-" + opt ); + } + // long option + else if( opt.startsWith( "--" ) ) { + option = (Option)longOpts.get( opt ); + } + // a just-in-case + else { + option = (Option)shortOpts.get( opt ); + } + + return (option == null) ? null : (Option)option.clone(); + } + + /** + *

Returns whether the named {@link Option} is a member + * of this {@link Options}

+ * + * @param opt short or long name of the {@link Option} + * @return true if the named {@link Option} is a member + * of this {@link Options} + */ + public boolean hasOption( String opt ) { + + // short option + if( opt.length() == 1 ) { + return shortOpts.containsKey( "-" + opt ); + } + // long option + else if( opt.startsWith( "--" ) ) { + return longOpts.containsKey( opt ); + } + // a just-in-case + else { + return shortOpts.containsKey( opt ); + } + } + + /**

Returns the OptionGroup the opt + * belongs to.

+ * @param opt the option whose OptionGroup is being queried. + * + * @return the OptionGroup if opt is part + * of an OptionGroup, otherwise return null + */ + public OptionGroup getOptionGroup( Option opt ) { + return (OptionGroup)optionGroups.get( opt.getOpt() ); + } + + /**

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + public String toString() { + StringBuffer buf = new StringBuffer(); + + buf.append("[ Options: [ short "); + buf.append( shortOpts.toString() ); + buf.append( " ] [ long " ); + buf.append( longOpts ); + buf.append( " ]"); + + return buf.toString(); + } +} diff --git a/installer/src/java/org/apache/commons/cli/ParseException.java b/installer/src/java/org/apache/commons/cli/ParseException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/ParseException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Base for Exceptions thrown during parsing of a command-line.

+ * + * @author bob mcwhirter (bob @ werken.com) + * @version $Revision: 2662 $ + */ +public class ParseException extends Exception +{ + + /** + *

Construct a new ParseException + * with the specified detail message.

+ * + * @param message the detail message + */ + public ParseException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Parser.java b/installer/src/java/org/apache/commons/cli/Parser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Parser.java @@ -0,0 +1,282 @@ +/* + * $Header$ + * $Revision: 2665 $ + * $Date: 2006-02-18 14:24:36 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + *

Parser creates {@link CommandLine}s.

+ * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2665 $ + */ +public abstract class Parser implements CommandLineParser { + + /** commandline instance */ + private CommandLine cmd; + /** current Options */ + private Options options; + /** list of required options strings */ + private List requiredOptions; + + /** + *

Subclasses must implement this method to reduce + * the arguments that have been passed to the parse + * method.

+ * + * @param opts The Options to parse the arguments by. + * @param args The arguments that have to be flattened. + * @param stopAtNonOption specifies whether to stop + * flattening when a non option has been encountered + * @return a String array of the flattened arguments + */ + abstract protected String[] flatten( Options opts, + String[] arguments, + boolean stopAtNonOption ); + + /** + *

Parses the specified arguments + * based on the specifed {@link Options}.

+ * + * @param options the Options + * @param arguments the arguments + * @return the CommandLine + * @throws ParseException if an error occurs when parsing the + * arguments. + */ + public CommandLine parse( Options options, String[] arguments ) + throws ParseException + { + return parse( options, arguments, false ); + } + + /** + *

Parses the specified arguments + * based on the specifed {@link Options}.

+ * + * @param options the Options + * @param arguments the arguments + * @param stopAtNonOption specifies whether to stop + * interpreting the arguments when a non option has + * been encountered and to add them to the CommandLines + * args list. + * @return the CommandLine + * @throws ParseException if an error occurs when parsing the + * arguments. + */ + public CommandLine parse( Options opts, + String[] arguments, + boolean stopAtNonOption ) + throws ParseException + { + // initialise members + options = opts; + requiredOptions = options.getRequiredOptions(); + cmd = new CommandLine(); + + boolean eatTheRest = false; + + List tokenList = Arrays.asList( flatten( opts, arguments, stopAtNonOption ) ); + ListIterator iterator = tokenList.listIterator(); + + // process each flattened token + while( iterator.hasNext() ) { + String t = (String)iterator.next(); + + // the value is the double-dash + if( "--".equals( t ) ) { + eatTheRest = true; + } + // the value is a single dash + else if( "-".equals( t ) ) { + if( stopAtNonOption ) { + eatTheRest = true; + } + else { + cmd.addArg(t ); + } + } + // the value is an option + else if( t.startsWith( "-" ) ) { + if ( stopAtNonOption && !options.hasOption( t ) ) { + eatTheRest = true; + cmd.addArg( t ); + } + else { + processOption( t, iterator ); + } + } + // the value is an argument + else { + cmd.addArg( t ); + if( stopAtNonOption ) { + eatTheRest = true; + } + } + + // eat the remaining tokens + if( eatTheRest ) { + while( iterator.hasNext() ) { + String str = (String)iterator.next(); + // ensure only one double-dash is added + if( !"--".equals( str ) ) { + cmd.addArg( str ); + } + } + } + } + checkRequiredOptions(); + return cmd; + } + + /** + *

Throws a {@link MissingOptionException} if all of the + * required options are no present.

+ */ + private void checkRequiredOptions() + throws MissingOptionException + { + + // if there are required options that have not been + // processsed + if( requiredOptions.size() > 0 ) { + Iterator iter = requiredOptions.iterator(); + StringBuffer buff = new StringBuffer(); + + // loop through the required options + while( iter.hasNext() ) { + buff.append( iter.next() ); + } + + throw new MissingOptionException( buff.toString() ); + } + } + + public void processArgs( Option opt, ListIterator iter ) + throws ParseException + { + // loop until an option is found + while( iter.hasNext() ) { + String var = (String)iter.next(); + + // found an Option + if( options.hasOption( var ) ) { + iter.previous(); + break; + } + // found a value + else if( !opt.addValue( var ) ) { + iter.previous(); + break; + } + } + + if( opt.getValues() == null && !opt.hasOptionalArg() ) { + throw new MissingArgumentException( "no argument for option: " + opt.getOpt() + " / " + opt.getLongOpt() ); + } + } + + private void processOption( String arg, ListIterator iter ) + throws ParseException + { + // get the option represented by arg + Option opt = null; + + boolean hasOption = options.hasOption( arg ); + + // if there is no option throw an UnrecognisedOptionException + if( !hasOption ) { + throw new UnrecognizedOptionException("Unrecognized option: " + arg); + } + else { + opt = (Option) options.getOption( arg ); + } + + // if the option is a required option remove the option from + // the requiredOptions list + if ( opt.isRequired() ) { + requiredOptions.remove( "-" + opt.getOpt() ); + } + + // if the option is in an OptionGroup make that option the selected + // option of the group + if ( options.getOptionGroup( opt ) != null ) { + OptionGroup group = ( OptionGroup ) options.getOptionGroup( opt ); + if( group.isRequired() ) { + requiredOptions.remove( group ); + } + group.setSelected( opt ); + } + + // if the option takes an argument value + if ( opt.hasArg() ) { + processArgs( opt, iter ); + } + + // set the option on the command line + cmd.addOption( opt ); + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java b/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java @@ -0,0 +1,204 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + * Allows Options to be created from a single String. + * + * + * @author Henri Yandell (bayard @ generationjava.com) + * @version $Revision: 2662 $ + */ +public class PatternOptionBuilder { + + /// TODO: These need to break out to OptionType and also to be pluggable. + + /** String class */ + public static final Class STRING_VALUE = java.lang.String.class; + /** Object class */ + public static final Class OBJECT_VALUE = java.lang.Object.class; + /** Number class */ + public static final Class NUMBER_VALUE = java.lang.Number.class; + /** Date class */ + public static final Class DATE_VALUE = java.util.Date.class; + /** Class class */ + public static final Class CLASS_VALUE = java.lang.Class.class; + +/// can we do this one?? +// is meant to check that the file exists, else it errors. +// ie) it's for reading not writing. + /** FileInputStream class */ + public static final Class EXISTING_FILE_VALUE = java.io.FileInputStream.class; + /** File class */ + public static final Class FILE_VALUE = java.io.File.class; + /** File array class */ + public static final Class FILES_VALUE = java.io.File[].class; + /** URL class */ + public static final Class URL_VALUE = java.net.URL.class; + + /** + *

Retrieve the class that ch represents.

+ * + * @param ch the specified character + * @return The class that ch represents + */ + public static Object getValueClass(char ch) { + if (ch == '@') { + return PatternOptionBuilder.OBJECT_VALUE; + } else if (ch == ':') { + return PatternOptionBuilder.STRING_VALUE; + } else if (ch == '%') { + return PatternOptionBuilder.NUMBER_VALUE; + } else if (ch == '+') { + return PatternOptionBuilder.CLASS_VALUE; + } else if (ch == '#') { + return PatternOptionBuilder.DATE_VALUE; + } else if (ch == '<') { + return PatternOptionBuilder.EXISTING_FILE_VALUE; + } else if (ch == '>') { + return PatternOptionBuilder.FILE_VALUE; + } else if (ch == '*') { + return PatternOptionBuilder.FILES_VALUE; + } else if (ch == '/') { + return PatternOptionBuilder.URL_VALUE; + } + return null; + } + + /** + *

Returns whether ch is a value code, i.e. + * whether it represents a class in a pattern.

+ * + * @param ch the specified character + * @return true if ch is a value code, otherwise false. + */ + public static boolean isValueCode(char ch) { + if( (ch != '@') && + (ch != ':') && + (ch != '%') && + (ch != '+') && + (ch != '#') && + (ch != '<') && + (ch != '>') && + (ch != '*') && + (ch != '/') + ) + { + return false; + } + return true; + } + + /** + *

Returns the {@link Options} instance represented by + * pattern.

+ * + * @param pattern the pattern string + * @return The {@link Options} instance + */ + public static Options parsePattern(String pattern) { + int sz = pattern.length(); + + char opt = ' '; + char ch = ' '; + boolean required = false; + Object type = null; + + Options options = new Options(); + + for(int i=0; i. + * + */ +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; + +/** + * The class PosixParser provides an implementation of the + * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2833 $ + */ +public class PosixParser extends Parser { + + /** holder for flattened tokens */ + private ArrayList tokens = new ArrayList(); + /** specifies if bursting should continue */ + private boolean eatTheRest; + /** holder for the current option */ + private Option currentOption; + /** the command line Options */ + private Options options; + + /** + *

Resets the members to their original state i.e. remove + * all of tokens entries, set eatTheRest + * to false and set currentOption to null.

+ */ + private void init() { + eatTheRest = false; + tokens.clear(); + currentOption = null; + } + + /** + *

An implementation of {@link Parser}'s abstract + * {@link Parser#flatten(Options,String[],boolean) flatten} method.

+ * + *

The following are the rules used by this flatten method. + *

    + *
  1. if stopAtNonOption is true then do not + * burst anymore of arguments entries, just add each + * successive entry without further processing. Otherwise, ignore + * stopAtNonOption.
  2. + *
  3. if the current arguments entry is "--" + * just add the entry to the list of processed tokens
  4. + *
  5. if the current arguments entry is "-" + * just add the entry to the list of processed tokens
  6. + *
  7. if the current arguments entry is two characters + * in length and the first character is "-" then check if this + * is a valid {@link Option} id. If it is a valid id, then add the + * entry to the list of processed tokens and set the current {@link Option} + * member. If it is not a valid id and stopAtNonOption + * is true, then the remaining entries are copied to the list of + * processed tokens. Otherwise, the current entry is ignored.
  8. + *
  9. if the current arguments entry is more than two + * characters in length and the first character is "-" then + * we need to burst the entry to determine its constituents. For more + * information on the bursting algorithm see + * {@link PosixParser#burstToken( String, boolean) burstToken}.
  10. + *
  11. if the current arguments entry is not handled + * by any of the previous rules, then the entry is added to the list + * of processed tokens.
  12. + *
+ *

+ * + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed + * @param stopAtNonOption Specifies whether to stop flattening + * when an non option is found. + * @return The flattened arguments String array. + */ + protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) { + init(); + this.options = options; + + // an iterator for the command line tokens + Iterator iter = Arrays.asList(arguments).iterator(); + String token = null; + + // process each command line token + while (iter.hasNext()) { + + // get the next command line token + token = (String) iter.next(); + + // handle SPECIAL TOKEN + if (token.startsWith("--")) { + processLongOptionToken(token, stopAtNonOption); + } else if ("-".equals(token)) { + // single hyphen + processSingleHyphen(token); + } else if (token.startsWith("-")) { + int tokenLength = token.length(); + if (tokenLength == 2) { + processOptionToken(token, stopAtNonOption); + } else { + boolean burst = true; + if (token.length() > 2) { + // check for misspelled long option + String longOptionCandidate = "-" + token; + if (this.options.hasOption(longOptionCandidate)) { + tokens.add(token); + burst = false; + } + } + if (burst) { + burstToken(token, stopAtNonOption); + } + } + } else { + if (stopAtNonOption) { + process(token); + } else { + tokens.add(token); + } + } + + gobble(iter); + } + + return (String[]) tokens.toArray(new String[] {}); + } + + /** + *

Adds the remaining tokens to the processed tokens list.

+ * + * @param iter An iterator over the remaining tokens + */ + private void gobble( Iterator iter ) { + if( eatTheRest ) { + while( iter.hasNext() ) { + tokens.add( iter.next() ); + } + } + } + + /** + *

If there is a current option and it can have an argument + * value then add the token to the processed tokens list and + * set the current option to null.

+ *

If there is a current option and it can have argument + * values then add the token to the processed tokens list.

+ *

If there is not a current option add the special token + * "--" and the current value to the processed + * tokens list. The add all the remaining argument + * values to the processed tokens list.

+ * + * @param value The current token + */ + private void process( String value ) { + if( currentOption != null && currentOption.hasArg() ) { + if( currentOption.hasArg() ) { + tokens.add( value ); + currentOption = null; + } + else if (currentOption.hasArgs() ) { + tokens.add( value ); + } + } + else { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( value ); + } + } + + /** + *

If it is a hyphen then add the hyphen directly to + * the processed tokens list.

+ * + * @param hyphen The hyphen token + */ + private void processSingleHyphen( String hyphen ) { + tokens.add( hyphen ); + } + + /** + *

If an {@link Option} exists for token then + * set the current option and add the token to the processed + * list.

+ *

If an {@link Option} does not exist and stopAtNonOption + * is set then ignore the current token and add the remaining tokens + * to the processed tokens list directly.

+ * + * @param token The current option token + * @param stopAtNonOption Specifies whether flattening should halt + * at the first non option. + */ + private void processOptionToken( String token, boolean stopAtNonOption ) { + if( this.options.hasOption( token ) ) { + currentOption = this.options.getOption( token ); + tokens.add( token ); + } else { + if( stopAtNonOption ) { + eatTheRest = true; + } else { + tokens.add(token); + } + } + } + + /** + * same stop logic as for single hyphen options + * @param longToken + * @param stopAtNonOption + */ + private void processLongOptionToken(String longToken, boolean stopAtNonOption ) { + String token; + String value = null; + if( longToken.indexOf( '=' ) != -1 ) { + token = longToken.substring( 0, longToken.indexOf( '=' )); + value = longToken.substring( longToken.indexOf( '=' ) + 1, longToken.length() ); + } else { + token = longToken; + } + if( this.options.hasOption(token)) { + tokens.add(token); + if( value != null) { + tokens.add(value); + } + } else { + if(stopAtNonOption) { + eatTheRest = true; + } else { + tokens.add(token); + if( value != null) { + tokens.add(value); + } + } + } + } + + + /** + *

Breaks token into its constituent parts + * using the following algorithm. + *

    + *
  • ignore the first character ("-" )
  • + *
  • foreach remaining character check if an {@link Option} + * exists with that id.
  • + *
  • if an {@link Option} does exist then add that character + * prepended with "-" to the list of processed tokens.
  • + *
  • if the {@link Option} can have an argument value and there + * are remaining characters in the token then add the remaining + * characters as a token to the list of processed tokens.
  • + *
  • if an {@link Option} does NOT exist AND + * stopAtNonOption IS set then add the special token + * "--" followed by the remaining characters and also + * the remaining tokens directly to the processed tokens list.
  • + *
  • if an {@link Option} does NOT exist AND + * stopAtNonOption IS NOT set then add that + * character prepended with "-".
  • + *
+ *

+ */ + protected void burstToken( String token, boolean stopAtNonOption ) { + int tokenLength = token.length(); + + for( int i = 1; i < tokenLength; i++) { + String ch = String.valueOf( token.charAt( i ) ); + boolean hasOption = options.hasOption( ch ); + + if( hasOption ) { + tokens.add( "-" + ch ); + currentOption = options.getOption( ch ); + if( currentOption.hasArg() && token.length()!=i+1 ) { + tokens.add( token.substring( i+1 ) ); + break; + } + } + else if( stopAtNonOption ) { + process( token.substring( i ) ); + } + else { + tokens.add( "-" + ch ); + } + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/TypeHandler.java b/installer/src/java/org/apache/commons/cli/TypeHandler.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/TypeHandler.java @@ -0,0 +1,252 @@ +/* + * $Header$ + * $Revision: 2664 $ + * $Date: 2006-02-18 14:20:35 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.io.File; +import java.math.BigDecimal; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Date; + +/** + * This is a temporary implementation. TypeHandler will handle the + * pluggableness of OptionTypes and it will direct all of these types + * of conversion functionalities to ConvertUtils component in Commons + * alreayd. BeanUtils I think. + * + * @author Henri Yandell (bayard @ generationjava.com) + * @version $Revision: 2664 $ + */ +public class TypeHandler { + + /** + *

Returns the Object of type obj + * with the value of str.

+ * + * @param str the command line value + * @param obj the type of argument + * @return The instance of obj initialised with + * the value of str. + */ + public static Object createValue(String str, Object obj) { + return createValue(str, (Class)obj); + } + + /** + *

Returns the Object of type clazz + * with the value of str.

+ * + * @param str the command line value + * @param clazz the type of argument + * @return The instance of clazz initialised with + * the value of str. + */ + public static Object createValue(String str, Class clazz) { + if( PatternOptionBuilder.STRING_VALUE == clazz) { + return str; + } else + if( PatternOptionBuilder.OBJECT_VALUE == clazz) { + return createObject(str); + } else + if( PatternOptionBuilder.NUMBER_VALUE == clazz) { + return createNumber(str); + } else + if( PatternOptionBuilder.DATE_VALUE == clazz) { + return createDate(str); + } else + if( PatternOptionBuilder.CLASS_VALUE == clazz) { + return createClass(str); + } else + if( PatternOptionBuilder.FILE_VALUE == clazz) { + return createFile(str); + } else + if( PatternOptionBuilder.EXISTING_FILE_VALUE == clazz) { + return createFile(str); + } else + if( PatternOptionBuilder.FILES_VALUE == clazz) { + return createFiles(str); + } else + if( PatternOptionBuilder.URL_VALUE == clazz) { + return createURL(str); + } else { + return null; + } + } + + /** + *

Create an Object from the classname and empty constructor.

+ * + * @param str the argument value + * @return the initialised object, or null if it couldn't create the Object. + */ + public static Object createObject(String str) { + Class cl = null; + try { + cl = Class.forName(str); + } catch (ClassNotFoundException cnfe) { + System.err.println("Unable to find: "+str); + return null; + } + + Object instance = null; + + try { + instance = cl.newInstance(); + } catch (InstantiationException cnfe) { + System.err.println("InstantiationException; Unable to create: "+str); + return null; + } + catch (IllegalAccessException cnfe) { + System.err.println("IllegalAccessException; Unable to create: "+str); + return null; + } + + return instance; + } + + /** + *

Create a number from a String.

+ * + * @param str the value + * @return the number represented by str, if str + * is not a number, null is returned. + */ + public static Number createNumber(String str) { + // Needs to be able to create + try { + // do searching for decimal point etc, but atm just make an Integer + return new BigDecimal(str); + } catch (NumberFormatException nfe) { + System.err.println(nfe.getMessage()); + return null; + } + } + + /** + *

Returns the class whose name is str.

+ * + * @param str the class name + * @return The class if it is found, otherwise return null + */ + public static Class createClass(String str) { + try { + return Class.forName(str); + } catch (ClassNotFoundException cnfe) { + System.err.println("Unable to find: "+str); + return null; + } + } + + /** + *

Returns the date represented by str.

+ * + * @param str the date string + * @return The date if str is a valid date string, + * otherwise return null. + */ + public static Date createDate(String str) { + Date date = null; + if(date == null) { + System.err.println("Unable to parse: "+str); + } + return date; + } + + /** + *

Returns the URL represented by str.

+ * + * @param str the URL string + * @return The URL is str is well-formed, otherwise + * return null. + */ + public static URL createURL(String str) { + try { + return new URL(str); + } catch (MalformedURLException mue) { + System.err.println("Unable to parse: "+str); + return null; + } + } + + /** + *

Returns the File represented by str.

+ * + * @param str the File location + * @return The file represented by str. + */ + public static File createFile(String str) { + return new File(str); + } + + /** + *

Returns the File[] represented by str.

+ * + * @param str the paths to the files + * @return The File[] represented by str. + */ + public static File[] createFiles(String str) { +// to implement/port: +// return FileW.findFiles(str); + return null; + } + +} diff --git a/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java b/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Exception thrown during parsing signalling an unrecognized + * option was seen.

+ * + * @author bob mcwhiter (bob @ werken.com) + * @version $Revision: 2662 $ + */ +public class UnrecognizedOptionException extends ParseException { + + /** + *

Construct a new UnrecognizedArgumentException + * with the specified detail message.

+ * + * @param message the detail message + */ + public UnrecognizedOptionException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/python/util/install/AbstractWizard.java b/installer/src/java/org/python/util/install/AbstractWizard.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizard.java @@ -0,0 +1,438 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.HeadlessException; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JSeparator; + +public abstract class AbstractWizard extends JDialog implements ValidationListener { + + private class WizardGlassPane extends JPanel implements MouseListener, KeyListener { + WizardGlassPane() { + super(); + setOpaque(false); + addMouseListener(this); + addKeyListener(this); + } + + public void keyPressed(KeyEvent e) { + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + public void mouseClicked(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseReleased(MouseEvent e) { + } + } + + private AbstractWizardPage _activePage = null; + private JPanel _buttonPanel; + private JSeparator _buttonSeparator; + private Action _cancelAction; + private JButton _cancelButton; + private CardLayout _cards; + private JPanel _content; + private WizardGlassPane _glassPane; + private AbstractWizardHeader _header; + private boolean _headerVisible = false; + private ArrayList _listeners; + private Action _nextAction; + private JButton _nextButton; + private ArrayList _pages; + private Action _previousAction; + private JButton _previousButton; + + public AbstractWizard() { + super(); + initWindow(); + initActions(); + initComponents(); + } + + public AbstractWizard(Dialog parent) throws HeadlessException { + super(parent); + initWindow(); + initActions(); + initComponents(); + } + + public AbstractWizard(Frame parent) throws HeadlessException { + super(parent); + initWindow(); + initActions(); + initComponents(); + } + + public final void addPage(AbstractWizardPage page) { + if (_pages == null) + _pages = new ArrayList(); + if (page == null || _pages.contains(page)) + return; + _pages.add(page); + page.setWizard(this); + int number = _pages.indexOf(page); + _content.add(page, "page" + number); + } + + public final void addWizardListener(WizardListener listener) { + if (listener == null) + return; + if (_listeners == null) + _listeners = new ArrayList(5); + if (_listeners.contains(listener)) + return; + _listeners.add(listener); + } + + private void cancel() { + fireCancelEvent(); + setVisible(false); + } + + /** + * @return whether the wizard finished succesfully + */ + protected abstract boolean finish(); + + private void fireCancelEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardCancelled(event); + } + } + + private void fireFinishedEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardFinished(event); + } + } + + private void fireNextEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardNext(event); + } + } + + private void firePreviousEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardPrevious(event); + } + } + + private void fireStartedEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardStarted(event); + } + } + + /** + * @return the String that will appear on the Cancel button + */ + protected abstract String getCancelString(); + + /** + * @return the String that will appear on the Finish button + */ + protected abstract String getFinishString(); + + /** + * @return the wizard header panel + */ + public AbstractWizardHeader getHeader() { + return _header; + } + + /** + * @return the String that will appear on the Next button + */ + protected abstract String getNextString(); + + /** + * @return the String that will appear on the Previous button + */ + protected abstract String getPreviousString(); + + /** + * usually only called from the WizardValidator, after validation succeeds + * + * validation always occurs when the 'next' or 'finish' button was clicked, so when the validation succeeds, the + * wizard can go to the next page + */ + public final void gotoNextPage() { + next(); + } + + private void initActions() { + _nextAction = new AbstractAction(getNextString()) { + public void actionPerformed(ActionEvent e) { + tryNext(); + } + }; + _previousAction = new AbstractAction(getPreviousString()) { + public void actionPerformed(ActionEvent e) { + previous(); + } + }; + _cancelAction = new AbstractAction(getCancelString()) { + public void actionPerformed(ActionEvent e) { + cancel(); + } + }; + } + + private void initComponents() { + _pages = new ArrayList(); + getContentPane().setLayout(new BorderLayout(0, 0)); + _header = new WizardHeader(); + getContentPane().add(_header, BorderLayout.NORTH); + _content = new JPanel(); + _cards = new CardLayout(); + _content.setLayout(_cards); + + getContentPane().add(_content, BorderLayout.CENTER); // was: WEST + + _buttonPanel = new JPanel(); + _buttonSeparator = new JSeparator(); + _cancelButton = new JButton(); + _previousButton = new JButton(); + _nextButton = new JButton(); + _cancelButton.setAction(_cancelAction); + _previousButton.setAction(_previousAction); + _nextButton.setAction(_nextAction); + GridBagConstraints gridBagConstraints; + _buttonPanel.setLayout(new GridBagLayout()); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new Insets(1, 1, 1, 1); + gridBagConstraints.weightx = 1.0; + _buttonPanel.add(_buttonSeparator, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + _buttonPanel.add(_cancelButton, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + _buttonPanel.add(_previousButton, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + _buttonPanel.add(_nextButton, gridBagConstraints); + getContentPane().add(_buttonPanel, BorderLayout.SOUTH); + } + + private void initWindow() { + _glassPane = new WizardGlassPane(); + setGlassPane(_glassPane); + } + + /** + * @return whether the wizard header is visible + */ + public final boolean isHeaderVisible() { + return _headerVisible; + } + + /** + * lock the wizard dialog, preventing any user input + */ + public final void lock() { + _glassPane.setVisible(true); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + + private void next() { + if (_activePage == null) + return; + _activePage.passivate(); + int activeIndex = _pages.indexOf(_activePage); + int nextIndex = activeIndex + 1; + if (nextIndex >= _pages.size()) { + tryFinish(); + return; + } else { + _activePage = (AbstractWizardPage) _pages.get(nextIndex); + showActivePage(); + } + fireNextEvent(); + } + + private void previous() { + if (_activePage == null) + return; + _activePage.passivate(); + int activeIndex = _pages.indexOf(_activePage); + int previousIndex = activeIndex - 1; + if (previousIndex < 0) + return; + else { + _activePage = (AbstractWizardPage) _pages.get(previousIndex); + showActivePage(); + } + firePreviousEvent(); + } + + public final void removeWizardListener(WizardListener listener) { + if (listener == null || _listeners == null || !_listeners.contains(listener)) + return; + _listeners.remove(listener); + } + + /** + * @param header the wizard header panel + */ + public void setHeader(AbstractWizardHeader header) { + if (this._header != null) { + getContentPane().remove(header); + } + this._header = header; + if (this._header != null) { + getContentPane().add(header, BorderLayout.NORTH); + } + } + + /** + * @param visible show the header in this wizard? + */ + public final void setHeaderVisible(boolean visible) { + _headerVisible = visible; + if (_header != null) + _header.setVisible(visible); + } + + @Override + public void setVisible(boolean visible) { + if (visible) { + fireStartedEvent(); + if (_pages.size() > 0) { + _activePage = (AbstractWizardPage) _pages.get(0); + showActivePage(); + } + } + super.setVisible(visible); + } + + private void showActivePage() { + if (_activePage == null) + return; + int number = _pages.indexOf(_activePage); + // show the active page + _cards.show(_content, "page" + number); + // update the wizard header + if (_header != null) { + _header.setTitle(_activePage.getTitle()); + _header.setDescription(_activePage.getDescription()); + _header.setIcon(_activePage.getIcon()); + } + // set visibility and localized text of buttons + if (number == 0) { + _previousButton.setVisible(false); + } else { + _previousButton.setVisible(_activePage.isPreviousVisible()); + } + _previousAction.putValue(Action.NAME, getPreviousString()); + _cancelButton.setVisible(_activePage.isCancelVisible()); + _cancelAction.putValue(Action.NAME, getCancelString()); + _nextButton.setVisible(_activePage.isNextVisible()); + _nextAction.putValue(Action.NAME, getNextString()); + if (number + 1 == _pages.size()) { + _nextAction.putValue(Action.NAME, getFinishString()); + } else { + _nextAction.putValue(Action.NAME, getNextString()); + } + if (_nextButton.isVisible()) { + getRootPane().setDefaultButton(_nextButton); + // workaround wrong default button (e.g. on OverviewPage) + if (_activePage.getFocusField() == null) { + _nextButton.grabFocus(); + } + } + _activePage.doActivate(); + } + + private void tryFinish() { + if (finish()) { + this.setVisible(false); + fireFinishedEvent(); + } + } + + private void tryNext() { + if (_activePage == null) + return; + _activePage.validateInput(); + } + + /** + * unlock the wizard dialog, allowing user input + */ + public final void unlock() { + _glassPane.setVisible(false); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardHeader.java b/installer/src/java/org/python/util/install/AbstractWizardHeader.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardHeader.java @@ -0,0 +1,12 @@ +package org.python.util.install; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; + +abstract class AbstractWizardHeader extends JPanel { + protected abstract void setTitle(String title); + + protected abstract void setDescription(String description); + + protected abstract void setIcon(ImageIcon icon); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardPage.java b/installer/src/java/org/python/util/install/AbstractWizardPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardPage.java @@ -0,0 +1,153 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.net.URL; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JPanel; + +public abstract class AbstractWizardPage extends JPanel implements TextKeys { + private static final long serialVersionUID = -5233805023557214279L; + + private static final String _ICON_FILE_NAME = "jython_small_c.png"; + + private static ImageIcon _imageIcon = null; + private AbstractWizardValidator _validator = null; + private AbstractWizard _wizard; + + public AbstractWizardPage() { + super(); + setValidator(null); + } + + /** + * This method is called when the wizard page is activated, after Wizard.next(); + */ + protected abstract void activate(); + + /** + * This method is called right before validation of the page + */ + protected abstract void beforeValidate(); + + /** + * called from Wizard, right after this page is set visible + */ + final void doActivate() { + if (getFocusField() != null) + this.getFocusField().grabFocus(); + activate(); + } + + /** + * @return the description of this page, which will be displayed in the wizard header + */ + protected abstract String getDescription(); + + /** + * @return the input field on the page that should grab the focus when the page is activated + */ + protected abstract JComponent getFocusField(); + + /** + * @return the icon that should be displayed in the header in all steps + */ + protected ImageIcon getIcon() { + if (_imageIcon == null) { + URL iconURL = FileHelper.getRelativeURL(getClass(), _ICON_FILE_NAME); + if (iconURL != null) { + _imageIcon = new ImageIcon(iconURL); + } + } + return _imageIcon; + } + + /** + * @return the title of this page, which will be displayed in the wizard header + */ + protected abstract String getTitle(); + + /** + * @return the wizard this page belongs to + */ + public final AbstractWizard getWizard() { + return _wizard; + } + + /** + * @return whether the cancel button is visible + */ + protected abstract boolean isCancelVisible(); + + /** + * @return whether the next button is visible + */ + protected abstract boolean isNextVisible(); + + /** + * @return whether the previous button is visible + */ + protected abstract boolean isPreviousVisible(); + + /** + * this method is called right before the page is hidden, but after the validation + */ + protected abstract void passivate(); + + /** + * Set the validator for this page. The validator is called when the next button is clicked. + * + * @param validator the validator for this page. If this is null, a EmptyValidator is assigned + */ + public final void setValidator(AbstractWizardValidator validator) { + if (validator == null) + this._validator = new EmptyValidator(); + else + this._validator = validator; + this._validator.setWizardPage(this); + this._validator.addValidationListener(_wizard); + } + + /** + * @param wizard the wizard this page belongs to + */ + final void setWizard(AbstractWizard wizard) { + this._wizard = wizard; + _validator.addValidationListener(wizard); + } + + /** + * perform the validation of this page, using the assigned WizardValidator + */ + final void validateInput() { + beforeValidate(); + if (_validator == null) + return; + _validator.start(); + } + + /** + * @return default grid bag constraints + */ + protected GridBagConstraints newGridBagConstraints() { + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + return gridBagConstraints; + } + + final String getText(String textKey) { + return Installation.getText(textKey); + } + + final String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + + final String getText(String textKey, String parameter0, String parameter1) { + return Installation.getText(textKey, parameter0, parameter1); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardValidator.java b/installer/src/java/org/python/util/install/AbstractWizardValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardValidator.java @@ -0,0 +1,132 @@ +package org.python.util.install; + +import java.util.ArrayList; +import java.util.Iterator; + +public abstract class AbstractWizardValidator implements TextKeys { + + /** + * The thread that performs the validation + */ + private class ValidatorThread extends Thread { + public final void run() { + try { + fireValidationStarted(); + validate(); + fireValidationSucceeded(); + } catch (ValidationException e) { + fireValidationFailed(e); + } catch (ValidationInformationException vie) { + fireValidationInformationRequired(vie); + } + } + } + + private ArrayList _listeners; + private AbstractWizardPage _page; + private Thread _validatorThread; + + public final void addValidationListener(ValidationListener listener) { + if (listener == null) + return; + if (_listeners == null) + _listeners = new ArrayList(5); + if (_listeners.contains(listener)) + return; + _listeners.add(listener); + } + + private void fireValidationFailed(ValidationException exception) { + if (getWizard() != null) + getWizard().unlock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationFailed(event, exception); + } + } + + private void fireValidationInformationRequired(ValidationInformationException exception) { + if (getWizard() != null) + getWizard().unlock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationInformationRequired(event, exception); + } + } + + private void fireValidationStarted() { + if (getWizard() != null) + getWizard().lock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationStarted(event); + } + } + + private void fireValidationSucceeded() { + if (getWizard() != null) { + getWizard().unlock(); + getWizard().gotoNextPage(); + } + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationSucceeded(event); + } + } + + protected final AbstractWizard getWizard() { + if (_page != null) { + return _page.getWizard(); + } else { + return null; + } + } + + protected final AbstractWizardPage getWizardPage() { + return _page; + } + + public final void removeValidationListener(ValidationListener listener) { + if (listener == null || _listeners == null || !_listeners.contains(listener)) + return; + _listeners.remove(listener); + } + + public final void setWizardPage(AbstractWizardPage page) { + this._page = page; + } + + public final void start() { + _validatorThread = new ValidatorThread(); + _validatorThread.start(); + } + + /** + * perform the actual validation + * + * @throws ValidationException when the validation failed + * @throws ValidationInformationException when an information should be displayed + */ + protected abstract void validate() throws ValidationException, ValidationInformationException; + + final String getText(String textKey) { + return Installation.getText(textKey); + } + + final String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ChildProcess.java b/installer/src/java/org/python/util/install/ChildProcess.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ChildProcess.java @@ -0,0 +1,357 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Easy start of a child process. + *

+ * Features are: + *

    + *
  • wait for the child process to finish. + *
  • kill the child process after a specified timeout. + *
  • get the output of the child process (System.out and System.err) redirected to the calling + * process, unless in silent mode. + *
+ */ +public class ChildProcess { + + /** + * Inner class for reading stdout of the child process and printing it onto the caller's stdout. + */ + private class StdoutMonitor extends Thread { + + private StdoutMonitor() {} + + public void run() { + String line = null; + BufferedReader stdout = new BufferedReader(new InputStreamReader(_process.getInputStream())); + try { + // blocks until input found or process dead + while ((line = stdout.readLine()) != null) { + if (!isSilent()) { + System.out.println(line); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } finally { + if (stdout != null) + try { + stdout.close(); + } catch (IOException e) {} + } + } + } + + /** + * Inner class for reading stderr of the child process and printing it onto the caller's stderr. + */ + private class StderrMonitor extends Thread { + + private StderrMonitor() {} + + public void run() { + String line = null; + BufferedReader stderr = new BufferedReader(new InputStreamReader(_process.getErrorStream())); + try { + // blocks until input found or process dead + while ((line = stderr.readLine()) != null) { + if (!isSilent()) { + System.err.println(line); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } finally { + if (stderr != null) + try { + stderr.close(); + } catch (IOException e) {} + } + } + } + + /** + * Constant indicating no timeout at all. + */ + public static final long INFINITE_TIMEOUT = -1; + + /** + * Constant indicating the exit value if the child process was destroyed due to a timeout. + */ + public static final int DESTROYED_AFTER_TIMEOUT = -9898; + + /** + * Constant indicating that the exit value was not yet set + */ + private static final int NOT_SET_EXITVALUE = -9; + + /** + * The command as an array of strings + */ + private String _command[] = null; + + /** + * The timeout (in milliseconds) + */ + private long _timeout = INFINITE_TIMEOUT; + + /** + * The interval for checking if the child process is still alive + */ + private long _pollAliveInterval = 1000; + + /** + * the effective child process + */ + Process _process; + + /** + * the exit value + */ + private int _exitValue = NOT_SET_EXITVALUE; + + /** + * The start time of the child process + */ + private long _startTime; + + /** + * debug option (default is false) + */ + private boolean _debug = false; + + /** + * silent flag + */ + private boolean _silent = false; + + /** + * Default constructor + */ + public ChildProcess() {} + + /** + * Constructor taking a command array as an argument + * + * @param command + * The command to be executed, every token as array element. + */ + public ChildProcess(String command[]) { + setCommand(command); + } + + /** + * Constructor taking a command array and the timeout as an argument + * + * @param command + * The command to be executed, every token as array element. + * @param timeout + * in milliseconds. Special value: INFINITE_TIMEOUT indicates no timeout + * at all. + */ + public ChildProcess(String command[], long timeout) { + setCommand(command); + setTimeout(timeout); + } + + /** + * Set the command array. This will override (but not overwrite) a previously set command + */ + public void setCommand(String command[]) { + _command = command; + } + + /** + * Returns the command array + */ + public String[] getCommand() { + return _command; + } + + /** + * Set the timeout (how long should the calling process wait for the child). + * + * @param timeout + * in milliseconds. Special value: INFINITE_TIMEOUT indicates no timeout + * at all. This is the default. + */ + public void setTimeout(long timeout) { + _timeout = timeout; + } + + /** + * Returns the timeout in milliseconds. + */ + public long getTimeout() { + return _timeout; + } + + /** + * Set the debug flag. + *

+ * Setting this to true will print the submitted command and an information if the child process + * is destroyed after the timeout. + */ + public void setDebug(boolean debug) { + _debug = debug; + } + + /** + * Returns the debug flag + */ + public boolean isDebug() { + return _debug; + } + + /** + * Set the silent flag. + *

+ * Setting this to true will suppress output of the called command. + */ + public void setSilent(boolean silent) { + _silent = silent; + } + + /** + * Returns the silent flag. + */ + public boolean isSilent() { + return _silent; + } + + /** + * Set the interval (in milliseconds) after which the subprocess is checked if it is still + * alive. Defaults to 1000 ms. + */ + public void setPollAliveInterval(long pollAliveInterval) { + _pollAliveInterval = pollAliveInterval; + } + + /** + * Returns the interval (in milliseconds) after which the subprocess is checked if it is still + * alive. + */ + public long getPollAliveInterval() { + return _pollAliveInterval; + } + + /** + * returns true if the timeout has expired + */ + private boolean isTimeout() { + boolean isTimeout = false; + long currentTime = System.currentTimeMillis(); + long diff = 0; + long timeout = getTimeout(); + if (timeout != INFINITE_TIMEOUT) { + diff = currentTime - _startTime; + if (diff > timeout) { + isTimeout = true; + } + } + return isTimeout; + } + + /** + * Start the child process + */ + public int run() { + try { + // determine start time + _startTime = System.currentTimeMillis(); + // start the process + _process = Runtime.getRuntime().exec(getCommand()); + debugCommand(); + // handle stdout and stderr + StdoutMonitor stdoutMonitor = new StdoutMonitor(); + stdoutMonitor.start(); + StderrMonitor stderrMonitor = new StderrMonitor(); + stderrMonitor.start(); + // run the subprocess as long as wanted + while (!isTimeout() && isAlive()) { + try { + Thread.sleep(getPollAliveInterval()); + } catch (InterruptedException ie) { + if (!isSilent()) { + ie.printStackTrace(); + } + } + } + // end properly + if (isAlive()) { // sets the exit value in case process is dead + destroy(); + } else { + if (isDebug()) { + System.out.println("[ChildProcess] ended itself"); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } + return getExitValue(); + } + + /** + * The exit value + */ + public int getExitValue() { + return _exitValue; + } + + private void setExitValue(int exitValue) { + _exitValue = exitValue; + } + + /** + * Tests if the process is still alive + */ + private boolean isAlive() { + try { + setExitValue(_process.exitValue()); + return false; + } catch (IllegalThreadStateException itse) { + return true; + } + } + + /** + * Destroy the child process + */ + private void destroy() { + _process.destroy(); + setExitValue(DESTROYED_AFTER_TIMEOUT); + if (isDebug()) { + System.out.println("[ChildProcess] destroying because of timeout !"); + } + } + + /** + * Lists the submitted command (if so indicated) + */ + private void debugCommand() { + if (isDebug()) { + String[] command = getCommand(); + if (command != null) { + System.out.print("[ChildProcess] command '"); + for (int i = 0; i < command.length; i++) { + String commandPart = command[i]; + if (i == 0) { + System.out.print(commandPart); + } else { + System.out.print(" " + commandPart); + } + } + System.out.println("' is now running..."); + } + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ConsoleInstaller.java b/installer/src/java/org/python/util/install/ConsoleInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ConsoleInstaller.java @@ -0,0 +1,609 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.python.util.install.Installation.JavaVersionInfo; +import org.python.util.install.driver.Tunnel; + +public class ConsoleInstaller implements ProgressListener, TextKeys { + + public static final String CURRENT_JRE = "="; + + private static final String _CANCEL = "c"; + + private static final String _PROMPT = ">>>"; + + private static final String _BEGIN_ANSWERS = "["; + + private static final String _END_ANSWERS = "]"; + + + private InstallerCommandLine _commandLine; + + private JarInstaller _jarInstaller; + + private JarInfo _jarInfo; + + private Tunnel _tunnel; + + public ConsoleInstaller(InstallerCommandLine commandLine, JarInfo jarInfo) { + _commandLine = commandLine; + _jarInfo = jarInfo; + _jarInstaller = new JarInstaller(this, jarInfo); + } + + public void setTunnel(Tunnel tunnel) { + _tunnel = tunnel; + } + + public void install() { + File targetDirectory = null; + JavaHomeHandler javaHomeHandler = null; + if (_commandLine.hasConsoleOption()) { + welcome(); + selectLanguage(); + acceptLicense(); + InstallationType installationType = selectInstallationType(); + targetDirectory = determineTargetDirectory(); + javaHomeHandler = checkVersion(determineJavaHome()); + promptForCopying(targetDirectory, installationType, javaHomeHandler); + _jarInstaller.inflate(targetDirectory, installationType, javaHomeHandler); + showReadme(targetDirectory); + success(targetDirectory); + } else if (_commandLine.hasSilentOption()) { + message(getText(C_SILENT_INSTALLATION)); + targetDirectory = _commandLine.getTargetDirectory(); + checkTargetDirectorySilent(targetDirectory); + javaHomeHandler = checkVersionSilent(_commandLine.getJavaHomeHandler()); + _jarInstaller.inflate(targetDirectory, + _commandLine.getInstallationType(), + javaHomeHandler); + success(targetDirectory); + } + } + + protected final static void message(String message) { + System.out.println(message); // this System.out.println is intended + } + + private void welcome() { + message(getText(C_WELCOME_TO_JYTHON)); + message(getText(C_VERSION_INFO, _jarInfo.getVersion())); + message(getText(C_AT_ANY_TIME_CANCEL, _CANCEL)); + } + + private String question(String question) { + return question(question, null, false, null); + } + + private String question(String question, boolean answerRequired) { + return question(question, null, answerRequired, null); + } + + private String question(String question, List answers, String defaultAnswer) { + return question(question, answers, true, defaultAnswer); + } + + /** + * question and answer + * + * @param question + * @param answers + * Possible answers (may be null) + * @param answerRequired + * @param defaultAnswer + * (may be null) + * + * @return (chosen) answer + */ + private String question(String question, + List answers, + boolean answerRequired, + String defaultAnswer) { + try { + if (answers != null && answers.size() > 0) { + question = question + " " + _BEGIN_ANSWERS; + Iterator answersAsIterator = answers.iterator(); + while (answersAsIterator.hasNext()) { + if (!question.endsWith(_BEGIN_ANSWERS)) + question = question + "/"; + String possibleAnswer = answersAsIterator.next(); + if (possibleAnswer.equalsIgnoreCase(defaultAnswer)) { + if (Character.isDigit(possibleAnswer.charAt(0))) { + question = question.concat(" ").concat(possibleAnswer).concat(" "); + } else { + question = question + possibleAnswer.toUpperCase(); + } + } else { + question = question + possibleAnswer; + } + } + question = question + _END_ANSWERS; + } + question = question + " " + _PROMPT + " "; + boolean match = false; + String answer = ""; + while (!match && !_CANCEL.equalsIgnoreCase(answer)) { + // output to normal System.out + System.out.print(question); // intended print, not println (!) + answer = readLine(); + if ("".equals(answer) && answerRequired) { + // check default answer + if (defaultAnswer != null) { + match = true; + answer = defaultAnswer; + } + } else { + if (answers != null && answers.size() > 0) { + Iterator answersAsIterator = answers.iterator(); + while (answersAsIterator.hasNext()) { + if (answer.equalsIgnoreCase(answersAsIterator.next())) { + match = true; + } + } + } else { + match = true; + } + if (!match && !_CANCEL.equalsIgnoreCase(answer)) { + message(getText(C_INVALID_ANSWER, answer)); + } + } + } + if (_CANCEL.equalsIgnoreCase(answer)) { + throw new InstallationCancelledException(); + } + return answer; + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + /** + * Send a signal through the tunnel, and then wait for the answer from the other side. + * + *

+     *            (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *            (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * 
+ */ + private String readLine() throws IOException { + InputStream inputStream; + String line = ""; + if (_tunnel == null) { + inputStream = System.in; + } else { + inputStream = _tunnel.getAnswerReceiverStream(); + _tunnel.getQuestionSenderStream().write(Tunnel.NEW_LINE.getBytes()); + _tunnel.getQuestionSenderStream().flush(); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + line = reader.readLine(); + return line; + } + + private void selectLanguage() { + List availableLanguages = new ArrayList(2); + availableLanguages.add(getText(C_ENGLISH)); + availableLanguages.add(getText(C_GERMAN)); // 1 == German + List answers = new ArrayList(availableLanguages.size()); + String languages = ""; + String defaultAnswer = null; + for (Iterator iterator = availableLanguages.iterator(); iterator.hasNext();) { + String language = iterator.next(); + String possibleAnswer = language.substring(0, 1); + if (defaultAnswer == null) { + defaultAnswer = possibleAnswer; + } + languages = languages + language + ", "; + answers.add(possibleAnswer.toLowerCase()); + } + languages = languages.substring(0, languages.length() - 2); + message(getText(C_AVAILABLE_LANGUAGES, languages)); + String answer = question(getText(C_SELECT_LANGUAGE), answers, defaultAnswer); + if (answer.equalsIgnoreCase(answers.get(1))) { + Installation.setLanguage(Locale.GERMAN); + } else { + Installation.setLanguage(Locale.ENGLISH); + } + } + + private InstallationType selectInstallationType() { + InstallationType installationType = new InstallationType(); + String no = getText(C_NO); + String yes = getText(C_YES); + message(getText(C_INSTALL_TYPES)); + message(" " + Installation.ALL + ". " + getText(C_ALL)); + message(" " + Installation.STANDARD + ". " + getText(C_STANDARD)); + message(" " + Installation.MINIMUM + ". " + getText(C_MINIMUM)); + message(" " + Installation.STANDALONE + ". " + getText(C_STANDALONE)); + String answer = question(getText(C_SELECT_INSTALL_TYPE), getTypeAnswers(), Installation.ALL); + if (Installation.ALL.equals(answer)) { + installationType.setAll(); + } else if (Installation.STANDARD.equals(answer)) { + installationType.setStandard(); + } else if (Installation.MINIMUM.equals(answer)) { + installationType.setMinimum(); + } else if (Installation.STANDALONE.equals(answer)) { + installationType.setStandalone(); + } + if (!installationType.isStandalone()) { + // include parts ? + if (!installationType.isAll()) { + answer = question(getText(C_INCLUDE), getYNAnswers(), no); + if (yes.equals(answer)) { + do { + answer = question(getText(C_INEXCLUDE_PARTS, no), getInExcludeAnswers(), no); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(answer)) { + installationType.addLibraryModules(); + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(answer)) { + installationType.addDemosAndExamples(); + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(answer)) { + installationType.addDocumentation(); + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(answer)) { + installationType.addSources(); + } + if (!no.equals(answer)) { + message(getText(C_SCHEDULED, answer)); + } + } while (!no.equals(answer)); + } + } + // exclude parts ? + if (!installationType.isMinimum()) { + answer = question(getText(C_EXCLUDE), getYNAnswers(), no); + if (yes.equals(answer)) { + do { + answer = question(getText(C_INEXCLUDE_PARTS, no), getInExcludeAnswers(), no); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(answer)) { + installationType.removeLibraryModules(); + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(answer)) { + installationType.removeDemosAndExamples(); + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(answer)) { + installationType.removeDocumentation(); + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(answer)) { + installationType.removeSources(); + } + if (!no.equals(answer)) { + message(getText(C_UNSCHEDULED, answer)); + } + } while (!no.equals(answer)); + } + } + } + return installationType; + } + + private JavaHomeHandler checkVersion(JavaHomeHandler javaHomeHandler) { + // handle target java version + JavaInfo javaInfo = verifyTargetJava(javaHomeHandler); + message(getText(C_JAVA_VERSION, + javaInfo.getJavaVersionInfo().getVendor(), + javaInfo.getJavaVersionInfo().getVersion())); + if (!Installation.isValidJava(javaInfo.getJavaVersionInfo())) { + message(getText(C_UNSUPPORTED_JAVA)); + question(getText(C_PROCEED_ANYWAY)); + } + // handle OS + String osName = System.getProperty(Installation.OS_NAME); + String osVersion = System.getProperty(Installation.OS_VERSION); + message(getText(C_OS_VERSION, osName, osVersion)); + if (!Installation.isValidOs()) { + message(getText(C_UNSUPPORTED_OS)); + question(getText(C_PROCEED_ANYWAY)); + } + return javaInfo.getJavaHomeHandler(); + } + + private JavaHomeHandler checkVersionSilent(JavaHomeHandler javaHomeHandler) { + // check target java version + JavaInfo javaInfo = verifyTargetJava(javaHomeHandler); + if (!Installation.isValidJava(javaInfo.getJavaVersionInfo())) { + message(getText(C_UNSUPPORTED_JAVA)); + } + // check OS + if (!Installation.isValidOs()) { + message(getText(C_UNSUPPORTED_OS)); + } + return javaInfo.getJavaHomeHandler(); + } + + private JavaInfo verifyTargetJava(JavaHomeHandler javaHomeHandler) { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); // a priori + if (javaHomeHandler.isDeviation()) { + javaVersionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (javaVersionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + // switch back to current if an error occurred + message(getText(C_TO_CURRENT_JAVA, javaVersionInfo.getReason())); + javaHomeHandler = new JavaHomeHandler(); + } + } + JavaInfo javaInfo = new JavaInfo(); + javaInfo.setJavaHomeHandler(javaHomeHandler); + javaInfo.setJavaVersionInfo(javaVersionInfo); + return javaInfo; + } + + private void acceptLicense() { + String no = getText(C_NO); + String yes = getText(C_YES); + String read = question(getText(C_READ_LICENSE), getYNAnswers(), no); + if (read.equalsIgnoreCase(getText(C_YES))) { + String licenseText = "n/a"; + try { + licenseText = _jarInfo.getLicenseText(); + message(licenseText); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + String accept = question(getText(C_ACCEPT), getYNAnswers(), yes); + if (!accept.equalsIgnoreCase(getText(C_YES))) { + throw new InstallationCancelledException(); + } + } + + private File determineTargetDirectory() { + String no = getText(C_NO); + String yes = getText(C_YES); + File targetDirectory = null; + try { + do { + targetDirectory = new File(question(getText(C_ENTER_TARGET_DIRECTORY), true)); + if (targetDirectory.exists()) { + if (!targetDirectory.isDirectory()) { + message(getText(C_NOT_A_DIRECTORY, targetDirectory.getCanonicalPath())); + } else { + if (targetDirectory.list().length > 0) { + String overwrite = question(getText(C_OVERWRITE_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + no); + if (overwrite.equalsIgnoreCase(getText(C_YES))) { + String clear = question(getText(C_CLEAR_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + yes); + if (clear.equalsIgnoreCase(getText(C_YES))) { + clearDirectory(targetDirectory); + } + } + } + } + } else { + String create = question(getText(C_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + yes); + if (create.equalsIgnoreCase(getText(C_YES))) { + if (!targetDirectory.mkdirs()) { + throw new InstallerException(getText(C_UNABLE_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } + } + } while (!targetDirectory.exists() || !targetDirectory.isDirectory() + || targetDirectory.list().length > 0); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + return targetDirectory; + } + + private JavaHomeHandler determineJavaHome() { + JavaHomeHandler javaHomeHandler = null; + boolean javaFound = false; + while (!javaFound) { + String javaHomeName = question(getText(C_ENTER_JAVA_HOME), null, true, CURRENT_JRE); + // only validate deviations + if (CURRENT_JRE.equals(javaHomeName)) { + javaHomeHandler = new JavaHomeHandler(); + javaFound = true; + } else { + javaHomeHandler = new JavaHomeHandler(javaHomeName); + if (javaHomeHandler.isDeviation()) { + if (!javaHomeHandler.isValidHome()) { + String binDirName = javaHomeName.concat("/bin"); + message(getText(C_NO_JAVA_EXECUTABLE, binDirName)); + } else { + javaFound = true; + } + } else { + javaFound = true; + } + } + } + return javaHomeHandler; + } + + private void checkTargetDirectorySilent(File targetDirectory) { + try { + if (!targetDirectory.exists()) { + // create directory + if (!targetDirectory.mkdirs()) { + throw new InstallerException(getText(C_UNABLE_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } else { + // assert it is an empty directory + if (!targetDirectory.isDirectory()) { + throw new InstallerException(getText(C_NOT_A_DIRECTORY, + targetDirectory.getCanonicalPath())); + } else { + if (targetDirectory.list().length > 0) { + throw new InstallerException(getText(C_NON_EMPTY_TARGET_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } + } + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + private void showReadme(final File targetDirectory) { + String no = getText(C_NO); + String read = question(getText(C_READ_README), getYNAnswers(), no); + if (read.equalsIgnoreCase(getText(C_YES))) { + try { + message(_jarInfo.getReadmeText()); + question(getText(C_PROCEED)); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + } + + private void clearDirectory(File targetDirectory) { + File files[] = targetDirectory.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + clearDirectory(files[i]); + } + if (!files[i].delete()) { + throw new InstallerException(getText(C_UNABLE_TO_DELETE, files[i].getAbsolutePath())); + } + } + } + + private void promptForCopying(final File targetDirectory, + final InstallationType installationType, + final JavaHomeHandler javaHomeHandler) { + try { + message(getText(C_SUMMARY)); + if (installationType.isStandalone()) { + message(" - " + InstallerCommandLine.TYPE_STANDALONE); + } else { + message(" - " + InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES + ": " + + installationType.installLibraryModules()); + message(" - " + InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES + ": " + + installationType.installDemosAndExamples()); + message(" - " + InstallerCommandLine.INEXCLUDE_DOCUMENTATION + ": " + + installationType.installDocumentation()); + message(" - " + InstallerCommandLine.INEXCLUDE_SOURCES + ": " + + installationType.installSources()); + if (javaHomeHandler.isValidHome()) { + message(" - JRE: " + javaHomeHandler.getHome().getAbsolutePath()); + } else { + message(" - java"); + } + } + String proceed = question(getText(C_CONFIRM_TARGET, targetDirectory.getCanonicalPath()), + getYNAnswers(), getText(C_YES)); + if (!proceed.equalsIgnoreCase(getText(C_YES))) { + throw new InstallationCancelledException(); + } + } catch (IOException ioe) { + throw new InstallerException(ioe); // catch for the compiler + } + } + + private void success(final File targetDirectory) { + try { + message(getText(C_CONGRATULATIONS) + " " + + getText(C_SUCCESS, _jarInfo.getVersion(), targetDirectory.getCanonicalPath())); + } catch (IOException ioe) { + throw new InstallerException(ioe); // catch for the compiler + } + } + + private List getTypeAnswers() { + List answers = new ArrayList(4); + answers.add(Installation.ALL); + answers.add(Installation.STANDARD); + answers.add(Installation.MINIMUM); + answers.add(Installation.STANDALONE); + return answers; + } + + private List getYNAnswers() { + List answers = new ArrayList(2); + answers.add(getText(C_YES)); + answers.add(getText(C_NO)); + return answers; + } + + private List getInExcludeAnswers() { + List answers = new ArrayList(5); + answers.add(InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES); + answers.add(InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES); + answers.add(InstallerCommandLine.INEXCLUDE_DOCUMENTATION); + answers.add(InstallerCommandLine.INEXCLUDE_SOURCES); + answers.add(getText(C_NO)); + return answers; + } + + private void progressMessage(int percentage) { + message(" " + percentage + " %"); + } + + private String getText(String textKey) { + return Installation.getText(textKey); + } + + private String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + + private String getText(String textKey, String parameter0, String parameter1) { + return Installation.getText(textKey, parameter0, parameter1); + } + + private static class JavaInfo { + + private JavaVersionInfo _javaVersionInfo; + + private JavaHomeHandler _javaHomeHandler; + + void setJavaHomeHandler(JavaHomeHandler javaHomeHandler) { + _javaHomeHandler = javaHomeHandler; + } + + JavaHomeHandler getJavaHomeHandler() { + return _javaHomeHandler; + } + + void setJavaVersionInfo(JavaVersionInfo javaVersionInfo) { + _javaVersionInfo = javaVersionInfo; + } + + JavaVersionInfo getJavaVersionInfo() { + return _javaVersionInfo; + } + } + + // + // interface ProgressListener + // + public void progressChanged(int newPercentage) { + progressMessage(newPercentage); + } + + public int getInterval() { + return 10; // fixed interval for console installer + } + + public void progressFinished() { + progressMessage(100); + } + + public void progressEntry(String entry) { + // ignore the single entries - only used in gui mode + } + + public void progressStartScripts() { + message(getText(C_GENERATING_START_SCRIPTS)); + } + + public void progressStandalone() { + message(getText(C_PACKING_STANDALONE_JAR)); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/DirectoryFilter.java b/installer/src/java/org/python/util/install/DirectoryFilter.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectoryFilter.java @@ -0,0 +1,23 @@ +package org.python.util.install; + +import java.io.File; + +import javax.swing.filechooser.FileFilter; + +/** + * Filters all directories, and sets the description in the file chooser + */ +public class DirectoryFilter extends FileFilter { + + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } else { + return false; + } + } + + public String getDescription() { + return Installation.getText(TextKeys.DIRECTORIES_ONLY); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/DirectorySelectionPage.java b/installer/src/java/org/python/util/install/DirectorySelectionPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectorySelectionPage.java @@ -0,0 +1,200 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +public class DirectorySelectionPage extends AbstractWizardPage { + + private static final long serialVersionUID = -3672273150338356549L; + + private JLabel _label; + private JTextField _directory; + private JButton _browse; + private JarInfo _jarInfo; + + public DirectorySelectionPage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + // label + _label = new JLabel(); + // directory + _directory = new JTextField(40); + _directory.addFocusListener(new DirectoryFocusListener()); + // browse button + _browse = new JButton(); + _browse.addActionListener(new BrowseButtonListener()); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(_directory, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(_browse, gridBagConstraints); + + add(panel); + } + + JTextField getDirectory() { + return _directory; + } + + protected String getTitle() { + return getText(TARGET_DIRECTORY_PROPERTY); + } + + protected String getDescription() { + return getText(CHOOSE_LOCATION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _directory; + } + + protected void activate() { + _label.setText(getText(SELECT_TARGET_DIRECTORY) + ": "); + _browse.setText(getText(BROWSE)); + String directory = FrameInstaller.getTargetDirectory(); + if (directory == null || directory.length() <= 0) { + File defaultDirectory = getDefaultDirectory(); + try { + directory = defaultDirectory.getCanonicalPath(); + } catch (IOException e) { + directory = "?"; + } + FrameInstaller.setTargetDirectory(directory); + } + _directory.setText(FrameInstaller.getTargetDirectory()); + _directory.setToolTipText(_directory.getText()); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private File getDefaultDirectory() { + String directory = ""; + File defaultDirectory = null; + // 1st try (on windows): root + if (Installation.isWindows()) { + JavaHomeHandler handler = new JavaHomeHandler(); + if (handler.isValidHome()) { + directory = handler.getHome().getAbsolutePath(); + if (directory.length() > 2) { + directory = directory.substring(0, 2); + } + } else { + directory = "C:"; + } + defaultDirectory = makeJythonSubDirectory(directory); + } + // 2st try: user.home + if (defaultDirectory == null) { + directory = System.getProperty("user.home", ""); + if (directory.length() > 0) { + defaultDirectory = makeJythonSubDirectory(directory); + } + } + // 3rd try: user.dir + if (defaultDirectory == null) { + directory = System.getProperty("user.dir", ""); + if (directory.length() > 0) { + defaultDirectory = makeJythonSubDirectory(directory); + } + } + // 4th try: current directory + if (defaultDirectory == null) { + defaultDirectory = makeJythonSubDirectory(new File(new File("dummy").getAbsolutePath()).getParent()); + } + return defaultDirectory; + } + + private File makeJythonSubDirectory(String directory) { + File defaultDirectory = null; + File parentDirectory = new File(directory); + if (parentDirectory.exists() && parentDirectory.isDirectory()) { + String jythonSubDirectoryName = "jython" + (_jarInfo.getVersion()).replaceAll("\\+", ""); + defaultDirectory = new File(parentDirectory, jythonSubDirectoryName); + } + return defaultDirectory; + } + + private class BrowseButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String directoryName = _directory.getText(); + File directory = new File(directoryName); + if (directory.exists()) { + if (!directory.isDirectory()) { + // switch to parent directory if user typed the name of a file + directory = directory.getParentFile(); + } + } + JFileChooser fileChooser = new JFileChooser(directory); + fileChooser.setDialogTitle(getText(SELECT_TARGET_DIRECTORY)); + // the filter is at the moment only used for the title of the dialog: + fileChooser.setFileFilter(new DirectoryFilter()); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (fileChooser.isAcceptAllFileFilterUsed()) { + if (Installation.isMacintosh() && Installation.isJDK141()) { + // work around ArrayIndexOutOfBoundsExceptio on Mac OS X, java version 1.4.1 + } else { + fileChooser.setAcceptAllFileFilterUsed(false); + } + } + int returnValue = fileChooser.showDialog(_browse, getText(SELECT)); + if (returnValue == JFileChooser.APPROVE_OPTION) { + _directory.setText(fileChooser.getSelectedFile().getAbsolutePath()); + _directory.setToolTipText(_directory.getText()); + FrameInstaller.setTargetDirectory(_directory.getText()); + } + } + } + + private class DirectoryFocusListener implements FocusListener { + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + FrameInstaller.setTargetDirectory(_directory.getText()); + _directory.setToolTipText(_directory.getText()); + } + } + +} diff --git a/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java b/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java @@ -0,0 +1,36 @@ +package org.python.util.install; + +import java.io.File; + +public class DirectorySelectionPageValidator extends AbstractWizardValidator { + + DirectorySelectionPage _page; + + DirectorySelectionPageValidator(DirectorySelectionPage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException, ValidationInformationException { + String directory = _page.getDirectory().getText().trim(); // trim to be sure + if (directory != null && directory.length() > 0) { + File targetDirectory = new File(directory); + if (targetDirectory.exists()) { + if (targetDirectory.isDirectory()) { + if (targetDirectory.list().length > 0) { + throw new ValidationException(getText(NON_EMPTY_TARGET_DIRECTORY)); + } + } + } else { + if (targetDirectory.mkdirs()) { + throw new ValidationInformationException(Installation.getText(CREATED_DIRECTORY, directory)); + } else { + throw new ValidationException(getText(UNABLE_CREATE_DIRECTORY, directory)); + } + } + } else { + throw new ValidationException(getText(EMPTY_TARGET_DIRECTORY)); + } + FrameInstaller.setTargetDirectory(directory); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/EmptyValidator.java b/installer/src/java/org/python/util/install/EmptyValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/EmptyValidator.java @@ -0,0 +1,8 @@ +package org.python.util.install; + +public class EmptyValidator extends AbstractWizardValidator { + + protected void validate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/FileHelper.java b/installer/src/java/org/python/util/install/FileHelper.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/FileHelper.java @@ -0,0 +1,208 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * Helper methods for file handling during installation / installation verification + */ +public final class FileHelper { + + private final static String EXECUTABLE_MODE = "755"; + + /** + * create a temporary directory with the same name as the passed in File (which may exist as + * file, not directory) + * + * @param tempDirectory + * @return true only if the the directory was successfully created (or already + * existed) + */ + public static boolean createTempDirectory(File tempDirectory) { + boolean success = true; + if (!tempDirectory.isDirectory()) { + if (tempDirectory.exists()) { + success = carryOnResult(tempDirectory.delete(), success); + } + if (success) { + success = tempDirectory.mkdirs(); + } + } + return success; + } + + /** + * completely remove a directory + * + * @param dir + * @return true if successful, false otherwise. + */ + public static boolean rmdir(File dir) { + boolean success = true; + if (dir.exists()) { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isFile()) { + success = carryOnResult(file.delete(), success); + } else { + if (file.isDirectory()) { + success = carryOnResult(rmdir(file), success); + } + } + } + success = carryOnResult(dir.delete(), success); + } + return success; + } + + /** + * read the contents of a file into a String + * + * @param file + * The file has to exist + * @return The contents of the file as String + * @throws IOException + */ + public static String readAll(File file) throws IOException { + FileReader fileReader = new FileReader(file); + try { + StringBuffer sb = new StringBuffer(); + char[] b = new char[8192]; + int n; + while ((n = fileReader.read(b)) > 0) { + sb.append(b, 0, n); + } + return sb.toString(); + } finally { + fileReader.close(); + } + } + + /** + * read the contents of a stream into a String + *

+ * ATTN: does not handle encodings + * + * @param inputStream + * The input stream + * @return A String representation of the file contents + * @throws IOException + */ + public static String readAll(InputStream inputStream) throws IOException { + try { + StringBuffer sb = new StringBuffer(); + byte[] b = new byte[8192]; + int n; + while ((n = inputStream.read(b)) > 0) { + sb.append(new String(b, 0, n)); + } + return sb.toString(); + } finally { + inputStream.close(); + } + } + + /** + * Write contents to a file. + *

+ * An existing file would be overwritten. + * + * @param file + * @param contents + * + * @throws IOException + */ + public static void write(File file, String contents) throws IOException { + FileWriter writer = new FileWriter(file); + writer.write(contents); + writer.flush(); + writer.close(); + } + + /** + * determine the url of a file relative to (in the same directory as) the specified .class file
+ * can also be used if the .class file resides inside a .jar file + * + * @param clazz + * The class next to the file + * @param fileName + * The name of the file + * + * @return The url of the file, can be null + */ + public static URL getRelativeURL(Class clazz, String fileName) { + String filePath = getRelativePackagePath(clazz) + "/" + fileName; + return Thread.currentThread().getContextClassLoader().getResource(filePath); + } + + /** + * get the input stream of a file relative to (in the same directory as) the specified .class + * file
+ * can also be used if the .class file resides inside a .jar file + * + * @param clazz + * The class next to the file + * @param fileName + * The name of the file + * + * @return The input stream of the file, can be null + */ + public static InputStream getRelativeURLAsStream(Class clazz, String fileName) { + String filePath = getRelativePackagePath(clazz) + "/" + fileName; + return Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath); + } + + /** + * do a chmod on the passed file + * + * @param scriptFile + */ + public static void makeExecutable(File scriptFile) { + try { + String command[] = new String[3]; + command[0] = "chmod"; + command[1] = EXECUTABLE_MODE; + command[2] = scriptFile.getAbsolutePath(); + long timeout = 3000; + ChildProcess childProcess = new ChildProcess(command, timeout); + childProcess.run(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * build the package path for the class loader
+ * the class loader should be able to load a file appended to this path, if it is in the same + * directory. + * + * @param clazz + * The class + * + * @return The package path + */ + private static String getRelativePackagePath(Class clazz) { + String className = clazz.getName(); + String packageName = className.substring(0, className.lastIndexOf(".")); + return packageName.replace('.', '/'); + } + + /** + * @param newResult + * @param existingResult + * @return false if newResult or existingResult are false, true + * otherwise. + */ + private static boolean carryOnResult(boolean newResult, boolean existingResult) { + if (existingResult) { + return newResult; + } else { + return existingResult; + } + } +} diff --git a/installer/src/java/org/python/util/install/FrameInstaller.java b/installer/src/java/org/python/util/install/FrameInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/FrameInstaller.java @@ -0,0 +1,186 @@ +package org.python.util.install; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Locale; +import java.util.Properties; + +import javax.swing.UIManager; + +import org.python.util.install.Installation.JavaVersionInfo; +import org.python.util.install.driver.Autotest; + +public class FrameInstaller { + private static final String TRUE = "1"; + private static final String FALSE = "0"; + + private static final String JAVA_VERSION_PROPERTY = "FrameInstaller.Version"; + private static final String JAVA_VENDOR_PROPERTY = "FrameInstaller.Vendor"; + private static final String JAVA_SPEC_VERSION_PROPERTY = "FrameInstaller.SpecVersion"; + + private static final String INEX_MOD_PROPERTY = "FrameInstaller.mod"; + private static final String INEX_DEMO_PROPERTY = "FrameInstaller.demo"; + private static final String INEX_DOC_PROPERTY = "FrameInstaller.doc"; + private static final String INEX_SRC_PROPERTY = "FrameInstaller.src"; + private static final String STANDALONE_PROPERTY = "FrameInstaller.standalone"; + + private static Properties _properties = new Properties(); + + private static JavaHomeHandler _javaHomeHandler; + + protected FrameInstaller(InstallerCommandLine commandLine, JarInfo jarInfo, Autotest autotest) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + } + // clear all properties + _properties.clear(); + // set the default for the target directory + if (commandLine.hasDirectoryOption()) { + setTargetDirectory(commandLine.getTargetDirectory().getAbsolutePath()); + } + if (commandLine.hasJavaHomeOption()) { + setJavaHomeHandler(commandLine.getJavaHomeHandler()); + } + initDefaultJava(); + Wizard wizard = new Wizard(jarInfo, autotest); + wizard.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent event) { + if (!Installation.isAutotesting()) { + System.exit(0); + } + } + }); + wizard.addWizardListener(new SimpleWizardListener()); + wizard.setVisible(true); + } + + protected static void setProperty(String key, String value) { + _properties.setProperty(key, value); + } + + protected static String getProperty(String key) { + return _properties.getProperty(key); + } + + protected static String getProperty(String key, String defaultValue) { + return _properties.getProperty(key, defaultValue); + } + + protected static void setTargetDirectory(String targetDirectory) { + setProperty(TextKeys.TARGET_DIRECTORY_PROPERTY, targetDirectory.trim()); + } + + protected static String getTargetDirectory() { + return getProperty(TextKeys.TARGET_DIRECTORY_PROPERTY); + } + + protected static void setJavaHomeHandler(JavaHomeHandler javaHomeHandler) { + _javaHomeHandler = javaHomeHandler; + } + + protected static JavaHomeHandler getJavaHomeHandler() { + if (_javaHomeHandler == null) { + _javaHomeHandler = new JavaHomeHandler(); + } + return _javaHomeHandler; + } + + protected static void setLanguage(Locale locale) { + setProperty(TextKeys.LANGUAGE_PROPERTY, locale.toString()); + Installation.setLanguage(locale); + } + + protected static Locale getLanguage() { + return new Locale(getProperty(TextKeys.LANGUAGE_PROPERTY)); + } + + protected static InstallationType getInstallationType() { + InstallationType installationType = new InstallationType(); + if (Boolean.valueOf(getProperty(STANDALONE_PROPERTY)).booleanValue()) { + installationType.setStandalone(); + } + if (Boolean.valueOf(getProperty(INEX_MOD_PROPERTY)).booleanValue()) { + installationType.addLibraryModules(); + } else { + installationType.removeLibraryModules(); + } + if (Boolean.valueOf(getProperty(INEX_DEMO_PROPERTY)).booleanValue()) { + installationType.addDemosAndExamples(); + } else { + installationType.removeDemosAndExamples(); + } + if (Boolean.valueOf(getProperty(INEX_DOC_PROPERTY)).booleanValue()) { + installationType.addDocumentation(); + } else { + installationType.removeDocumentation(); + } + if (Boolean.valueOf(getProperty(INEX_SRC_PROPERTY)).booleanValue()) { + installationType.addSources(); + } else { + installationType.removeSources(); + } + return installationType; + } + + protected static void setInstallationType(InstallationType installationType) { + setProperty(STANDALONE_PROPERTY, Boolean.toString(installationType.isStandalone())); + setProperty(INEX_MOD_PROPERTY, Boolean.toString(installationType.installLibraryModules())); + setProperty(INEX_DEMO_PROPERTY, Boolean.toString(installationType.installDemosAndExamples())); + setProperty(INEX_DOC_PROPERTY, Boolean.toString(installationType.installDocumentation())); + setProperty(INEX_SRC_PROPERTY, Boolean.toString(installationType.installSources())); + } + + protected static JavaVersionInfo getJavaVersionInfo() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + javaVersionInfo.setVersion(getProperty(JAVA_VERSION_PROPERTY)); + javaVersionInfo.setVendor(getProperty(JAVA_VENDOR_PROPERTY)); + javaVersionInfo.setSpecificationVersion(getProperty(JAVA_SPEC_VERSION_PROPERTY)); + return javaVersionInfo; + } + + protected static void setJavaVersionInfo(JavaVersionInfo javaVersionInfo) { + setProperty(JAVA_VERSION_PROPERTY, javaVersionInfo.getVersion()); + setProperty(JAVA_VENDOR_PROPERTY, javaVersionInfo.getVendor()); + setProperty(JAVA_SPEC_VERSION_PROPERTY, javaVersionInfo.getSpecificationVersion()); + } + + protected static void setAccept(boolean accept) { + if (accept) { + setProperty(TextKeys.ACCEPT_PROPERTY, TRUE); + } else { + setProperty(TextKeys.ACCEPT_PROPERTY, FALSE); + } + } + + protected static boolean isAccept() { + return TRUE.equals(getProperty(TextKeys.ACCEPT_PROPERTY, FALSE)); + } + + protected static void initDefaultJava() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); + FrameInstaller.setJavaVersionInfo(javaVersionInfo); + } + + private class SimpleWizardListener implements WizardListener { + public void wizardStarted(WizardEvent event) { + } + + public void wizardFinished(WizardEvent event) { + if (!Installation.isAutotesting()) { + System.exit(0); + } + } + + public void wizardCancelled(WizardEvent event) { + System.exit(1); + } + + public void wizardNext(WizardEvent event) { + } + + public void wizardPrevious(WizardEvent event) { + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/Installation.java b/installer/src/java/org/python/util/install/Installation.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/Installation.java @@ -0,0 +1,439 @@ +package org.python.util.install; + +import java.awt.GraphicsEnvironment; // should be allowed on headless systems +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +import org.python.util.install.driver.Autotest; +import org.python.util.install.driver.InstallationDriver; +import org.python.util.install.driver.Tunnel; + +public class Installation { + public final static int NORMAL_RETURN = 0; + public final static int ERROR_RETURN = 1; + + protected static final String ALL = "1"; + protected static final String STANDARD = "2"; + protected static final String MINIMUM = "3"; + protected static final String STANDALONE = "9"; + + protected static final String OS_NAME = "os.name"; + protected static final String OS_VERSION = "os.version"; + protected static final String JAVA_VM_NAME = "java.vm.name"; + protected static final String EMPTY = ""; + + protected static final String HEADLESS_PROPERTY_NAME = "java.awt.headless"; + + private static final String RESOURCE_CLASS = "org.python.util.install.TextConstants"; + + private static ResourceBundle _textConstants = ResourceBundle.getBundle(RESOURCE_CLASS, Locale.getDefault()); + + private static boolean _verbose = false; + private static boolean _isAutotesting = false; + + public static void main(String args[]) { + internalMain(args, null, null); + } + + public static void driverMain(String args[], Autotest autotest, Tunnel tunnel) { + internalMain(args, autotest, tunnel); + } + + protected static boolean isVerbose() { + return _verbose; + } + + protected static void setVerbose(boolean verbose) { + _verbose = verbose; + } + + protected static boolean isAutotesting() { + return _isAutotesting; + } + + protected static String getText(String key) { + return _textConstants.getString(key); + } + + protected static String getText(String key, String... parameters) { + return MessageFormat.format(_textConstants.getString(key), (Object[])parameters); + } + + protected static void setLanguage(Locale locale) { + _textConstants = ResourceBundle.getBundle(RESOURCE_CLASS, locale); + } + + public static boolean isValidOs() { + String osName = System.getProperty(OS_NAME, ""); + String lowerOs = osName.toLowerCase(); + if (isWindows()) { + return true; + } + if (lowerOs.indexOf("linux") >= 0) { + return true; + } + if (lowerOs.indexOf("mac") >= 0) { + return true; + } + if (lowerOs.indexOf("unix") >= 0) { + return true; + } + return false; + } + + protected static boolean isValidJava(JavaVersionInfo javaVersionInfo) { + String specificationVersion = javaVersionInfo.getSpecificationVersion(); + verboseOutput("specification version: '" + specificationVersion + "'"); + boolean valid = true; + if (getJavaSpecificationVersion(specificationVersion) < 15) { + valid = false; + } + return valid; + } + + /** + * @return specification version as an int, e.g. 15 or 16 (the micro part is ignored) + * @param specificationVersion + * as system property + */ + public static int getJavaSpecificationVersion(String specificationVersion) { + // major.minor.micro + // according to http://java.sun.com/j2se/1.5.0/docs/guide/versioning/spec/versioning2.html + String major = "1"; + String minor = "0"; + StringTokenizer tokenizer = new StringTokenizer(specificationVersion, "."); + if (tokenizer.hasMoreTokens()) { + major = tokenizer.nextToken(); + } + if (tokenizer.hasMoreTokens()) { + minor = tokenizer.nextToken(); + } + return Integer.valueOf(major.concat(minor)).intValue(); + } + + public static boolean isWindows() { + boolean isWindows = false; + String osName = System.getProperty(OS_NAME, ""); + if (osName.toLowerCase().indexOf("windows") >= 0) { + isWindows = true; + } + return isWindows; + } + + protected static boolean isMacintosh() { + boolean isMacintosh = false; + String osName = System.getProperty(OS_NAME, ""); + if (osName.toLowerCase().indexOf("mac") >= 0) { + isMacintosh = true; + } + return isMacintosh; + } + + protected static boolean isGNUJava() { + boolean isGNUJava = false; + String javaVmName = System.getProperty(JAVA_VM_NAME, ""); + String lowerVmName = javaVmName.toLowerCase(); + if (lowerVmName.indexOf("gnu") >= 0 && lowerVmName.indexOf("libgcj") >= 0) { + isGNUJava = true; + } + return isGNUJava; + } + + protected static boolean isJDK141() { + boolean isJDK141 = false; + String javaVersion = System.getProperty(JavaVersionTester.JAVA_VERSION, ""); + if (javaVersion.toLowerCase().startsWith("1.4.1")) { + isJDK141 = true; + } + return isJDK141; + } + + /** + * Get the version info of an external (maybe other) jvm. + * + * @param javaHomeHandler + * The java home handler pointing to the java home of the external jvm.
+ * The /bin directory is assumed to be a direct child directory. + * + * @return The versionInfo + */ + protected static JavaVersionInfo getExternalJavaVersion(JavaHomeHandler javaHomeHandler) { + JavaVersionInfo versionInfo = new JavaVersionInfo(); + if (javaHomeHandler.isValidHome()) { + try { + ConsoleInstaller.message(getText(TextKeys.C_CHECK_JAVA_VERSION)); + // launch the java command - temporary file will be written by the child process + File tempFile = File.createTempFile("jython_installation", ".properties"); + if (tempFile.exists() && tempFile.canWrite()) { + String command[] = new String[5]; + command[0] = javaHomeHandler.getExecutableName(); + command[1] = "-cp"; + // our own class path should be ok here + command[2] = System.getProperty("java.class.path"); + command[3] = JavaVersionTester.class.getName(); + command[4] = tempFile.getAbsolutePath(); + verboseOutput("executing: " + command[0] + " " + command[1] + " " + command[2] + + " " + command[3] + " " + command[4]); + ChildProcess childProcess = new ChildProcess(command, 10000); // 10 seconds + childProcess.setDebug(Installation.isVerbose()); + int errorCode = childProcess.run(); + if (errorCode != NORMAL_RETURN) { + versionInfo.setErrorCode(errorCode); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } else { + Properties tempProperties = new Properties(); + tempProperties.load(new FileInputStream(tempFile)); + fillJavaVersionInfo(versionInfo, tempProperties); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_UNABLE_CREATE_TMPFILE, tempFile.getAbsolutePath())); + } + } catch (IOException e) { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } + + return versionInfo; + } + + /** + * @return The system default java version + */ + public static JavaVersionInfo getDefaultJavaVersion() { + JavaVersionInfo versionInfo = new JavaVersionInfo(); + String executableName = "java"; + try { + // launch the java command - temporary file will be written by the child process + File tempFile = File.createTempFile("jython_installation", ".properties"); + if (tempFile.exists() && tempFile.canWrite()) { + String command[] = new String[5]; + command[0] = executableName; + command[1] = "-cp"; + // our own class path should be ok here + command[2] = System.getProperty("java.class.path"); + command[3] = JavaVersionTester.class.getName(); + command[4] = tempFile.getAbsolutePath(); + ChildProcess childProcess = new ChildProcess(command, 10000); // 10 seconds + childProcess.setDebug(false); + int errorCode = childProcess.run(); + if (errorCode != NORMAL_RETURN) { + versionInfo.setErrorCode(errorCode); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, executableName)); + } else { + Properties tempProperties = new Properties(); + tempProperties.load(new FileInputStream(tempFile)); + fillJavaVersionInfo(versionInfo, tempProperties); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_UNABLE_CREATE_TMPFILE, + tempFile.getAbsolutePath())); + } + } catch (IOException e) { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, executableName)); + } + return versionInfo; + } + + protected static void fillJavaVersionInfo(JavaVersionInfo versionInfo, Properties properties) { + versionInfo.setVersion(properties.getProperty(JavaVersionTester.JAVA_VERSION)); + versionInfo.setSpecificationVersion(properties.getProperty(JavaVersionTester.JAVA_SPECIFICATION_VERSION)); + versionInfo.setVendor(properties.getProperty(JavaVersionTester.JAVA_VENDOR)); + } + + public static class JavaVersionInfo { + private String _version; + private String _specificationVersion; + private String _vendor; + private int _errorCode; + private String _reason; + + protected JavaVersionInfo() { + _version = EMPTY; + _specificationVersion = EMPTY; + _errorCode = NORMAL_RETURN; + _reason = EMPTY; + } + + protected void setVersion(String version) { + _version = version; + } + + protected void setSpecificationVersion(String specificationVersion) { + _specificationVersion = specificationVersion; + } + + protected void setVendor(String vendor) { + _vendor = vendor; + } + + protected void setErrorCode(int errorCode) { + _errorCode = errorCode; + } + + protected void setReason(String reason) { + _reason = reason; + } + + protected String getVersion() { + return _version; + } + + public String getSpecificationVersion() { + return _specificationVersion; + } + + protected String getVendor() { + return _vendor; + } + + public int getErrorCode() { + return _errorCode; + } + + protected String getReason() { + return _reason; + } + } + + protected static class JavaFilenameFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + if (name.toLowerCase().startsWith("java")) { + return true; + } else { + return false; + } + } + } + + public static boolean isGuiAllowed() { + verboseOutput("checking gui availability"); + if (Boolean.getBoolean(HEADLESS_PROPERTY_NAME)) { + verboseOutput(HEADLESS_PROPERTY_NAME + " is true"); + return false; + } else if (GraphicsEnvironment.isHeadless()) { + verboseOutput("GraphicsEnvironment is headless"); + return false; + } else { + try { + verboseOutput("trying to get the GraphicsEnvironment"); + GraphicsEnvironment.getLocalGraphicsEnvironment(); + verboseOutput("got the GraphicsEnvironment!"); + return true; + } catch (Throwable t) { + verboseOutput("got the following exception:"); + verboseOutput(t); + return false; + } + } + } + + // + // private methods + // + + private static boolean useGui(InstallerCommandLine commandLine) { + if (commandLine.hasConsoleOption() || commandLine.hasSilentOption()) { + return false; + } + return isGuiAllowed(); + } + + /** + * In the normal case, this method is called with (args, null, null), see main(args). + *

+ * However, in autotesting mode (commandLine.hasAutotestOption()), we pass in Autotest and Tunnel, + * see driverMain(args, autotest, tunnel). + *

+ * This means that in autotesting mode this method will call itself (via InstallationDriver.drive()), + * but with different arguments. + */ + private static void internalMain(String[] args, Autotest autotest, Tunnel tunnel) { + try { + setVerbose(InstallerCommandLine.hasVerboseOptionInArgs(args)); + dumpSystemProperties(); + verboseOutput("reading jar info"); + JarInfo jarInfo = new JarInfo(); + InstallerCommandLine commandLine = new InstallerCommandLine(jarInfo); + if (!commandLine.setArgs(args) || commandLine.hasHelpOption()) { + commandLine.printHelp(); + System.exit(1); + } else { + if (commandLine.hasAutotestOption()) { + verboseOutput("running autotests"); + _isAutotesting = true; + InstallationDriver autotestDriver = new InstallationDriver(commandLine); + autotestDriver.drive(); // ! reentrant into internalMain() + _isAutotesting = false; + ConsoleInstaller.message("\ncongratulations - autotests complete !"); + System.exit(0); + } + if (!useGui(commandLine)) { + verboseOutput("using the console installer"); + ConsoleInstaller consoleInstaller = new ConsoleInstaller(commandLine, jarInfo); + consoleInstaller.setTunnel(tunnel); + consoleInstaller.install(); + if (!isAutotesting()) { + System.exit(0); + } + } else { + verboseOutput("using the gui installer"); + new FrameInstaller(commandLine, jarInfo, autotest); + } + } + } catch (InstallationCancelledException ice) { + ConsoleInstaller.message((getText(TextKeys.INSTALLATION_CANCELLED))); + System.exit(1); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } + + private static void dumpSystemProperties() throws IOException { + if (isVerbose()) { + @SuppressWarnings("unchecked") + Enumeration names = (Enumeration)System.getProperties().propertyNames(); + StringBuilder contents = new StringBuilder(400); + contents.append("Properties at the beginning of the Jython installation:\n\n"); + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = System.getProperty(name, ""); + contents.append(name); + contents.append('='); + contents.append(value); + contents.append("\n"); + } + File output = File.createTempFile("System", ".properties"); + FileHelper.write(output, contents.toString()); + ConsoleInstaller.message("system properties dumped to " + output.getAbsolutePath()); + } + } + + private static void verboseOutput(String message) { + if (isVerbose()) { + ConsoleInstaller.message(message); + } + } + + private static void verboseOutput(Throwable t) { + if (isVerbose()) { + t.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/InstallationCancelledException.java b/installer/src/java/org/python/util/install/InstallationCancelledException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationCancelledException.java @@ -0,0 +1,9 @@ +package org.python.util.install; + +public class InstallationCancelledException extends InstallerException { + + public InstallationCancelledException() { + super(); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/InstallationListener.java b/installer/src/java/org/python/util/install/InstallationListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationListener.java @@ -0,0 +1,7 @@ +package org.python.util.install; + +public interface InstallationListener { + + public void progressFinished(); + +} diff --git a/installer/src/java/org/python/util/install/InstallationType.java b/installer/src/java/org/python/util/install/InstallationType.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationType.java @@ -0,0 +1,120 @@ +package org.python.util.install; + +public class InstallationType { + + private boolean _installLibraryModules = true; + private boolean _installDemosAndExamples = true; + private boolean _installDocumentation = true; + private boolean _installSources = false; + private boolean _isStandalone = false; + + public boolean installLibraryModules() { + return _installLibraryModules; + } + + public boolean installDemosAndExamples() { + return _installDemosAndExamples; + } + + public boolean installDocumentation() { + return _installDocumentation; + } + + public boolean installSources() { + return _installSources; + } + + public void addLibraryModules() { + _installLibraryModules = true; + } + + public void removeLibraryModules() { + _installLibraryModules = false; + } + + public void addDemosAndExamples() { + _installDemosAndExamples = true; + } + + public void removeDemosAndExamples() { + _installDemosAndExamples = false; + } + + public void addDocumentation() { + _installDocumentation = true; + } + + public void removeDocumentation() { + _installDocumentation = false; + } + + public void addSources() { + _installSources = true; + } + + public void removeSources() { + _installSources = false; + } + + public void setStandalone() { + _isStandalone = true; + addLibraryModules(); + removeDemosAndExamples(); + removeDocumentation(); + removeSources(); + } + + public boolean isStandalone() { + return _isStandalone; + } + + public void setAll() { + addLibraryModules(); + addDemosAndExamples(); + addDocumentation(); + addSources(); + _isStandalone = false; + } + + public boolean isAll() { + return installLibraryModules() && installDemosAndExamples() && installDocumentation() && installSources(); + } + + public void setStandard() { + addLibraryModules(); + addDemosAndExamples(); + addDocumentation(); + removeSources(); + _isStandalone = false; + } + + public boolean isStandard() { + return installLibraryModules() && installDemosAndExamples() && installDocumentation() && !installSources(); + } + + public void setMinimum() { + removeLibraryModules(); + removeDemosAndExamples(); + removeDocumentation(); + removeSources(); + _isStandalone = false; + } + + public boolean isMinimum() { + return !installLibraryModules() && !installDemosAndExamples() && !installDocumentation() && !installSources(); + } + + /** + * @return true if current settings reflect one of the predefined settings + */ + public boolean isPredefined() { + return isAll() || isStandard() || isMinimum() || isStandalone(); + } + + public String toString() { + StringBuffer buf = new StringBuffer(30); + buf.append("mod: " + installDemosAndExamples() + ", demo: " + installDemosAndExamples() + ", doc: " + + installDocumentation() + ", src: " + installSources()); + return buf.toString(); + } +} diff --git a/installer/src/java/org/python/util/install/InstallerCommandLine.java b/installer/src/java/org/python/util/install/InstallerCommandLine.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallerCommandLine.java @@ -0,0 +1,456 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.MissingArgumentException; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.Parser; +import org.apache.commons.cli.PosixParser; + +public class InstallerCommandLine { + protected static final String INEXCLUDE_LIBRARY_MODULES = "mod"; + protected static final String INEXCLUDE_DEMOS_AND_EXAMPLES = "demo"; + protected static final String INEXCLUDE_DOCUMENTATION = "doc"; + protected static final String INEXCLUDE_SOURCES = "src"; + + protected static final String CONSOLE_SHORT = "c"; + protected static final String CONSOLE_LONG = "console"; + private static final String CONSOLE_DESC = "console based installation (user interaction)\n" + + "any other options will be ignored (except 'verbose')"; + + protected static final String SILENT_SHORT = "s"; + protected static final String SILENT_LONG = "silent"; + private static final String SILENT_DESC = "silent installation (without user interaction)"; + + protected static final String VERBOSE_SHORT = "v"; + protected static final String VERBOSE_LONG = "verbose"; + private static final String VERBOSE_DESC = "print more output during the installation\n" + + "(also valid in GUI and autotest mode)"; + + private static final String JRE_SHORT = "j"; + private static final String JRE_LONG = "jre"; + private static final String JRE_DESC = "home directory of the runtime jre or jdk\n" + + "(executables are assumed in the /bin subdirectory)\n" + "select this if you want to run Jython with a\n" + + "different java version than the installation"; + + private static final String AUTOTEST_SHORT = "A"; + private static final String AUTOTEST_LONG = "autotest"; + private static final String AUTOTEST_DESC = "automatic stress tests for the installer\n" + + "most of the other options are ignored\n" + "allowed additional options: '" + VERBOSE_LONG + "', '" + + JRE_LONG + "'"; + + private static final String DIRECTORY_SHORT = "d"; + private static final String DIRECTORY_LONG = "directory"; + private static final String DIRECTORY_DESC = "target directory to install to\n" + + "(required in silent mode,\nused as default in GUI mode)"; + + private static final String DIRECTORY_ARG = "dir"; + + private static final String TYPE_STANDARD = "standard"; + private static final String TYPE_ALL = "all"; + private static final String TYPE_MINIMUM = "minimum"; + protected static final String TYPE_STANDALONE = "standalone"; + private static final String STANDALONE_DOCUMENTATION = "install a single, executable .jar,\ncontaining all the modules"; + + private static final String INEXCLUDE_ARG = "part(s)"; + private static final String INEXCLUDE_PARTS = "more than one of the following is possible:\n" + "- " + + INEXCLUDE_LIBRARY_MODULES + ": library modules\n" + "- " + INEXCLUDE_DEMOS_AND_EXAMPLES + + ": demos and examples\n" + "- " + INEXCLUDE_DOCUMENTATION + ": documentation\n" + "- " + + INEXCLUDE_SOURCES + ": java source code"; + + private static final String TYPE_SHORT = "t"; + private static final String TYPE_LONG = "type"; + private static final String TYPE_ARG = TYPE_LONG; + private static final String TYPE_DESC = "installation type\n" + "one of the following types is possible\n" + + "(see also include/exclude parts):\n" + "- " + TYPE_ALL + ": everything (including " + INEXCLUDE_SOURCES + + ")\n" + "- " + TYPE_STANDARD + ": core, " + INEXCLUDE_LIBRARY_MODULES + ", " + + INEXCLUDE_DEMOS_AND_EXAMPLES + ", " + INEXCLUDE_DOCUMENTATION + ",\n"+ TYPE_STANDARD+ " is the default\n" + "- " + TYPE_MINIMUM + ": core\n" + + "- " + TYPE_STANDALONE + ": " + STANDALONE_DOCUMENTATION; + + private static final String INCLUDE_SHORT = "i"; + private static final String INCLUDE_LONG = "include"; + private static final String INCLUDE_DESC = "finer control over parts to install\n" + INEXCLUDE_PARTS; + + private static final String EXCLUDE_SHORT = "e"; + private static final String EXCLUDE_LONG = "exclude"; + private static final String EXCLUDE_DESC = "finer control over parts not to install\n" + INEXCLUDE_PARTS + + "\n(excludes override includes)"; + + private static final String HELP_SHORT = "h"; + private static final String HELP2_SHORT = "?"; + private static final String HELP_LONG = "help"; + private static final String HELP_DESC = "print this help (overrides any other options)"; + + private static final String SYNTAX = "\n\tjava -jar jython_version.jar"; + private static final String HEADER = "\nNo option at all will start the interactive GUI installer, except:\n" + + "Options respected in GUI mode are '" + DIRECTORY_LONG + "' and '" + JRE_LONG + + "', which serve as default values in the wizard.\n" + + "In non-GUI mode the following options are available:\n."; + private static final String SYNTAX_WITHOUT_JAR = "\n\tjava -jar "; + private static final String FOOTER = ""; + private static final String EXAMPLES = "\nexample of a GUI installation:{0}" + + "\n\nexample of a console installation:{0} -" + CONSOLE_SHORT + + "\n\nexample of a silent installation:{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + " targetDirectory" + + "\n\nexamples of a silent installation with more options:{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + + " targetDirectory -" + TYPE_SHORT + " " + TYPE_MINIMUM + " -" + INCLUDE_SHORT + " " + INEXCLUDE_SOURCES + + " -" + JRE_SHORT + " javaHome" + "{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + " targetDirectory -" + + TYPE_SHORT + " " + TYPE_STANDARD + " -" + EXCLUDE_SHORT + " " + INEXCLUDE_DEMOS_AND_EXAMPLES + " " + + INEXCLUDE_DOCUMENTATION + "\n\t\t -" + INCLUDE_SHORT + " " + INEXCLUDE_SOURCES + " -" + JRE_SHORT + + " javaHome -" + VERBOSE_SHORT + + "\n\nexample of an autotest installation into temporary directories:{0} -" + AUTOTEST_SHORT + + "\n\t(make sure you do NOT touch mouse NOR keyboard after hitting enter/return!)" + + "\n\nexample of an autotest installation, using a different jre for the start scripts:{0} -" + + AUTOTEST_SHORT + " -" + JRE_SHORT + " javaHome" + " -" + VERBOSE_SHORT + + "\n\t(make sure you do NOT touch mouse NOR keyboard after hitting enter/return!)"; + + private String[] _args; + private Options _options; + private CommandLine _commandLine; + private JarInfo _jarInfo; + private final Parser _parser = new PosixParser(); + + public InstallerCommandLine(JarInfo jarInfo) { + createOptions(); + _jarInfo = jarInfo; + } + + /** + * Pre-scan of the arguments to detect a verbose flag + * @param args + * @return true if there is a verbose option + */ + public static final boolean hasVerboseOptionInArgs(String[] args) { + String shortVerbose = "-".concat(VERBOSE_SHORT); + String longVerbose = "--".concat(VERBOSE_LONG); + return hasOptionInArgs(args, shortVerbose, longVerbose); + } + + /** + * constructor intended for JUnit tests only. + */ + public InstallerCommandLine() { + this(null); + } + + /** + * Set the arguments from the command line. + * + * @param args the arguments of the command line + * @return true if all arguments are valid, false otherwise. No help is printed if + * false is returned + */ + public boolean setArgs(String args[]) { + // pre-process args to determine if we can (and should) switch to console mode + try { + CommandLine preCommandLine = _parser.parse(_options, args, false); + if (!hasConsoleOption(preCommandLine) && !hasSilentOption(preCommandLine) + && !hasAutotestOption(preCommandLine)) { + if (!Installation.isGuiAllowed() || Installation.isGNUJava()) { + // auto switch to console mode + if (hasVerboseOption(preCommandLine)) { + ConsoleInstaller.message("auto-switching to console mode"); + } + String[] newArgs = new String[args.length + 1]; + System.arraycopy(args, 0, newArgs, 0, args.length); + newArgs[args.length] = "-" + CONSOLE_SHORT; + args = newArgs; + } + } + } catch (Exception e) { + // ignore + } + _args = args; + try { + // throws for missing or unknown options / arguments + _commandLine = _parser.parse(_options, _args, false); + } catch (MissingArgumentException mae) { + System.err.println(mae.getMessage()); + return false; + } catch (ParseException pe) { + System.err.println(pe.getMessage()); + return false; + } + List unrecognized = _commandLine.getArgList(); + if (unrecognized.size() > 0) { + System.err.println("unrecognized argument(s): " + unrecognized); + return false; + } + if (hasTypeOption()) { + String type = _commandLine.getOptionValue(TYPE_SHORT); + if (TYPE_ALL.equals(type) || TYPE_STANDARD.equals(type) || TYPE_MINIMUM.equals(type) + || TYPE_STANDALONE.equals(type)) { + } else { + System.err.println("unrecognized argument '" + type + "' to option: " + TYPE_SHORT + " / " + TYPE_LONG); + return false; + } + } + if (hasSilentOption()) { + if (!hasDirectoryOption()) { + System.err.println("option " + DIRECTORY_SHORT + " / " + DIRECTORY_LONG + " is required in " + + SILENT_LONG + " mode"); + return false; + } + } + if (hasIncludeOption()) { + String[] includeParts = _commandLine.getOptionValues(INCLUDE_SHORT); + for (int i = 0; i < includeParts.length; i++) { + if (!isValidInExcludePart(includeParts[i])) { + System.err.println("unrecognized include part '" + includeParts[i] + "'"); + return false; + } + } + } + if (hasExcludeOption()) { + String[] excludeParts = _commandLine.getOptionValues(EXCLUDE_SHORT); + for (int i = 0; i < excludeParts.length; i++) { + if (!isValidInExcludePart(excludeParts[i])) { + System.err.println("unrecognized exclude part '" + excludeParts[i] + "'"); + return false; + } + } + } + return true; + } + + public boolean hasArguments() { + return _args.length > 0; + } + + public boolean hasHelpOption() { + return _commandLine.hasOption(HELP_SHORT) || _commandLine.hasOption(HELP2_SHORT) + || _commandLine.hasOption(HELP_LONG); + } + + public boolean hasSilentOption() { + return hasSilentOption(_commandLine); + } + + private boolean hasSilentOption(CommandLine commandLine) { + return commandLine.hasOption(SILENT_SHORT) || commandLine.hasOption(SILENT_LONG); + } + + public boolean hasConsoleOption() { + return hasConsoleOption(_commandLine); + } + + private boolean hasConsoleOption(CommandLine commandLine) { + return commandLine.hasOption(CONSOLE_SHORT) || commandLine.hasOption(CONSOLE_LONG); + } + + public boolean hasAutotestOption() { + return hasAutotestOption(_commandLine); + } + + private boolean hasAutotestOption(CommandLine commandLine) { + return commandLine.hasOption(AUTOTEST_SHORT) || commandLine.hasOption(AUTOTEST_LONG); + } + + public boolean hasDirectoryOption() { + return _commandLine.hasOption(DIRECTORY_SHORT) || _commandLine.hasOption(DIRECTORY_LONG); + } + + public boolean hasTypeOption() { + return _commandLine.hasOption(TYPE_SHORT) || _commandLine.hasOption(TYPE_LONG); + } + + public boolean hasIncludeOption() { + return _commandLine.hasOption(INCLUDE_SHORT) || _commandLine.hasOption(INCLUDE_LONG); + } + + public boolean hasExcludeOption() { + return _commandLine.hasOption(EXCLUDE_SHORT) || _commandLine.hasOption(EXCLUDE_LONG); + } + + public boolean hasJavaHomeOption() { + return _commandLine.hasOption(JRE_SHORT) || _commandLine.hasOption(JRE_LONG); + } + + public boolean hasVerboseOption() { + return hasVerboseOption(_commandLine); + } + + private boolean hasVerboseOption(CommandLine commandLine) { + return commandLine.hasOption(VERBOSE_SHORT) || commandLine.hasOption(VERBOSE_LONG); + } + + public void printHelp() { + HelpFormatter formatter = new HelpFormatter(); + formatter.defaultWidth = 76; + String syntax = SYNTAX; + if (_jarInfo != null) { + try { + syntax = SYNTAX_WITHOUT_JAR + _jarInfo.getJarFile().getName(); + } catch (IOException ioe) { + } + } + formatter.printHelp(syntax, HEADER, _options, FOOTER, true); + String examples = MessageFormat.format(EXAMPLES, syntax); + System.out.println(examples); + } + + /** + * @return the requested target directory, null if no directory specified + */ + public File getTargetDirectory() { + if (hasDirectoryOption()) { + return new File(_commandLine.getOptionValue(DIRECTORY_SHORT)); + } else { + return null; + } + } + + /** + * @return a java home handler for the requested java home directory, or a default handler if no + * java home specified + */ + public JavaHomeHandler getJavaHomeHandler() { + if (hasJavaHomeOption()) { + return new JavaHomeHandler(_commandLine.getOptionValue(JRE_SHORT)); + } else { + return new JavaHomeHandler(); + } + } + + /** + * The Installation type is built out of the type, include and exclude option + * + * @return the installation type usable for the jar installer + */ + public InstallationType getInstallationType() { + InstallationType installationType = new InstallationType(); // defaults to standard + // build a priori values out of the type option + if (hasTypeOption()) { + String typeName = _commandLine.getOptionValue(TYPE_SHORT); + if (TYPE_ALL.equals(typeName)) { + installationType.setAll(); + } else if (TYPE_MINIMUM.equals(typeName)) { + installationType.setMinimum(); + } else if (TYPE_STANDALONE.equals(typeName)) { + installationType.setStandalone(); + } + } + // add parts to include + if (hasIncludeOption()) { + String[] includeParts = _commandLine.getOptionValues(INCLUDE_SHORT); + for (int i = 0; i < includeParts.length; i++) { + if (INEXCLUDE_DEMOS_AND_EXAMPLES.equals(includeParts[i])) { + installationType.addDemosAndExamples(); + } + if (INEXCLUDE_DOCUMENTATION.equals(includeParts[i])) { + installationType.addDocumentation(); + } + if (INEXCLUDE_LIBRARY_MODULES.equals(includeParts[i])) { + installationType.addLibraryModules(); + } + if (INEXCLUDE_SOURCES.equals(includeParts[i])) { + installationType.addSources(); + } + } + } + // remove parts to exclude + if (hasExcludeOption()) { + String[] excludeParts = _commandLine.getOptionValues(EXCLUDE_SHORT); + for (int i = 0; i < excludeParts.length; i++) { + if (INEXCLUDE_DEMOS_AND_EXAMPLES.equals(excludeParts[i])) { + installationType.removeDemosAndExamples(); + } + if (INEXCLUDE_DOCUMENTATION.equals(excludeParts[i])) { + installationType.removeDocumentation(); + } + if (INEXCLUDE_LIBRARY_MODULES.equals(excludeParts[i])) { + installationType.removeLibraryModules(); + } + if (INEXCLUDE_SOURCES.equals(excludeParts[i])) { + installationType.removeSources(); + } + } + } + return installationType; + } + + // + // private methods + // + + private static final boolean hasOptionInArgs(String[] args, String shortOption, String longOption) { + boolean hasOption = false; + int i = 0; + while (!hasOption && i < args.length) { + if (shortOption.equals(args[i]) || longOption.equals(args[i])) { + hasOption = true; + } + i++; + } + return hasOption; + } + + private void createOptions() { + _options = new Options(); + _options.setSortAsAdded(true); + + // console or silent mode + Option consoleOption = new Option(CONSOLE_SHORT, CONSOLE_LONG, false, CONSOLE_DESC); + Option silentOption = new Option(SILENT_SHORT, SILENT_LONG, false, SILENT_DESC); + Option autotestOption = new Option(AUTOTEST_SHORT, AUTOTEST_LONG, false, AUTOTEST_DESC); + OptionGroup group1 = new OptionGroup(); + group1.addOption(consoleOption); + group1.addOption(silentOption); + group1.addOption(autotestOption); + _options.addOptionGroup(group1); + + // target directory + Option directoryOption = new Option(DIRECTORY_SHORT, DIRECTORY_LONG, true, DIRECTORY_DESC); + directoryOption.setArgName(DIRECTORY_ARG); + _options.addOption(directoryOption); + + // installation type + Option typeOption = new Option(TYPE_SHORT, TYPE_LONG, true, TYPE_DESC); + typeOption.setArgName(TYPE_ARG); + _options.addOption(typeOption); + + // additional parts to include + Option includeOption = new Option(INCLUDE_SHORT, INCLUDE_DESC); + includeOption.setArgs(4); + includeOption.setArgName(INEXCLUDE_ARG); + includeOption.setLongOpt(INCLUDE_LONG); + _options.addOption(includeOption); + + // parts to exclude + Option excludeOption = new Option(EXCLUDE_SHORT, EXCLUDE_DESC); + excludeOption.setArgs(4); + excludeOption.setArgName(INEXCLUDE_ARG); + excludeOption.setLongOpt(EXCLUDE_LONG); + _options.addOption(excludeOption); + + // runtime jre + Option jreOption = new Option(JRE_SHORT, JRE_LONG, true, JRE_DESC); + jreOption.setArgName(DIRECTORY_ARG); + _options.addOption(jreOption); + + // verbose + Option verboseOption = new Option(VERBOSE_SHORT, VERBOSE_LONG, false, VERBOSE_DESC); + _options.addOption(verboseOption); + + // different help options + Option helpHOption = new Option(HELP_SHORT, HELP_LONG, false, HELP_DESC); + Option helpQOption = new Option(HELP2_SHORT, HELP_DESC); + OptionGroup group2 = new OptionGroup(); + group2.addOption(helpHOption); + group2.addOption(helpQOption); + _options.addOptionGroup(group2); + } + + private boolean isValidInExcludePart(String part) { + return INEXCLUDE_DEMOS_AND_EXAMPLES.equals(part) || INEXCLUDE_DOCUMENTATION.equals(part) + || INEXCLUDE_LIBRARY_MODULES.equals(part) || INEXCLUDE_SOURCES.equals(part); + } + +} diff --git a/installer/src/java/org/python/util/install/InstallerException.java b/installer/src/java/org/python/util/install/InstallerException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallerException.java @@ -0,0 +1,21 @@ +package org.python.util.install; + +public class InstallerException extends RuntimeException { + + public InstallerException() { + super(); + } + + public InstallerException(String message) { + super(message); + } + + public InstallerException(String message, Throwable cause) { + super(message, cause); + } + + public InstallerException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JarInfo.java b/installer/src/java/org/python/util/install/JarInfo.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JarInfo.java @@ -0,0 +1,235 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class JarInfo { + private static final String JAR_URL_PREFIX = "jar:file:"; + private static final String JAR_SEPARATOR = "!"; + private static final String JYTHON = "Jython"; + private static final String VERSION_ATTRIBUTE = "version"; + private static final String EXCLUDE_DIRS_ATTRIBUTE = "exclude-dirs"; + private static final String EXCLUDE_DIRS_DELIM = ";"; + + private File _jarFile; + private int _numberOfEntries; + private Manifest _manifest; + private String _licenseText; + private String _readmeText; + + public JarInfo() { + _jarFile = null; + _numberOfEntries = 0; + _manifest = null; + + try { + readJarInfo(); + } catch (IOException ioe) { + throw new InstallerException(Installation.getText(TextKeys.ERROR_ACCESS_JARFILE), ioe); + } + } + + public String getVersion() { + String version = ""; + try { + Attributes jythonAttributes = getManifest().getAttributes(JYTHON); + if (jythonAttributes != null) { + version = jythonAttributes.getValue(VERSION_ATTRIBUTE); // do + // not + // use + // containsKey + } + } catch (IOException ioe) { + } + return version; + } + + public File getJarFile() throws IOException { + if (_jarFile == null) + readJarInfo(); + return _jarFile; + } + + public Manifest getManifest() throws IOException { + if (_manifest == null) + readJarInfo(); + return _manifest; + } + + public int getNumberOfEntries() throws IOException { + if (_numberOfEntries == 0) + readJarInfo(); + return _numberOfEntries; + } + + public List getExcludeDirs() throws IOException { + List excludeDirs = new ArrayList(); + Attributes jythonAttributes = getManifest().getAttributes(JYTHON); + if (jythonAttributes != null) { + // do not use containsKey + String excludeDirsString = jythonAttributes.getValue(EXCLUDE_DIRS_ATTRIBUTE); + if (excludeDirsString != null && excludeDirsString.length() > 0) { + StringTokenizer tokenizer = new StringTokenizer(excludeDirsString, EXCLUDE_DIRS_DELIM); + while (tokenizer.hasMoreTokens()) { + excludeDirs.add(tokenizer.nextToken()); + } + } + } + return excludeDirs; + } + + public String getLicenseText() throws IOException { + if (_licenseText == null) { + readJarInfo(); + } + return _licenseText; + } + + public String getReadmeText() throws IOException { + if (_readmeText == null) { + readJarInfo(); + } + return _readmeText; + } + + private void readJarInfo() throws IOException { + String fullClassName = getClass().getName(); + String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1); + URL url = getClass().getResource(className + ".class"); + // we expect an URL like: + // jar:file:/C:/stuff/jython21i.jar!/org/python/util/install/JarInfo.class + // escape plus signs, since the URLDecoder would turn them into spaces + final String plus = "\\+"; + final String escapedPlus = "__ppluss__"; + String rawUrl = url.toString(); + rawUrl = rawUrl.replaceAll(plus, escapedPlus); + String urlString = URLDecoder.decode(rawUrl, "UTF-8"); + urlString = urlString.replaceAll(escapedPlus, plus); + int jarSeparatorIndex = urlString.lastIndexOf(JAR_SEPARATOR); + if (!urlString.startsWith(JAR_URL_PREFIX) || jarSeparatorIndex <= 0) { + throw new InstallerException(Installation.getText(TextKeys.UNEXPECTED_URL, urlString)); + } + String jarFileName = urlString.substring(JAR_URL_PREFIX.length(), jarSeparatorIndex); + _jarFile = new File(jarFileName); + if (!_jarFile.exists()) { + throw new InstallerException(Installation.getText(TextKeys.JAR_NOT_FOUND, _jarFile.getAbsolutePath())); + } + JarFile jarFile = new JarFile(_jarFile); + Enumeration entries = jarFile.entries(); + _numberOfEntries = 0; + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + if ("LICENSE.txt".equals(entry.getName())) { + _licenseText = readTextFile(entry, jarFile); + } + if ("README.txt".equals(entry.getName())) { + _readmeText = readTextFile(entry, jarFile); + } + _numberOfEntries++; + } + _manifest = jarFile.getManifest(); + if (_manifest == null) { + throw new InstallerException(Installation.getText(TextKeys.NO_MANIFEST, _jarFile.getAbsolutePath())); + } + jarFile.close(); + } + + /** + * Read the text file with the most appropriate Charset. + * + * @param entry + * @param jarFile + * + * @return the contents of the text file + * + * @throws IOException + */ + private String readTextFile(JarEntry entry, JarFile jarFile) throws IOException { + String contents = readTextFileWithCharset(jarFile, entry, "US-ASCII"); // expected to run on most platforms + if (contents == null) { + contents = readTextFileWithCharset(jarFile, entry, "ISO-8859-1"); + } + if (contents == null) { + contents = readTextFileWithDefaultCharset(jarFile, entry); + } + return contents; + } + + /** + * Try to read the text file (jarEntry) from the jarFile, using a given charsetName. + * + * @param jarFile + * @param entry + * @param charsetName the name of the Charset + * + * @return the contents of the text file as String (if reading was successful), null otherwise.
+ * No exception is thrown + */ + private String readTextFileWithCharset(JarFile jarFile, JarEntry entry, String charsetName) { + String contents = null; + if (Charset.isSupported(charsetName)) { + BufferedReader reader = null; + try { + StringBuffer buffer = new StringBuffer(1000); + reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry), Charset + .forName(charsetName))); + buffer = new StringBuffer(1000); + for (String s; (s = reader.readLine()) != null;) { + buffer.append(s); + buffer.append("\n"); + } + contents = buffer.toString(); + } catch (IOException ioe) { + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + } + } + } + return contents; + } + + /** + * Read the text file (jarEntry) from the jarFile, using the platform default Charset. + * + * @param jarFile + * @param entry + * + * @return the contents of the text file as String. + * + * @throws IOException if a problem occurs + */ + private String readTextFileWithDefaultCharset(JarFile jarFile, JarEntry entry) throws IOException { + String contents = null; + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry))); + StringBuffer buffer = new StringBuffer(1000); + for (String s; (s = reader.readLine()) != null;) { + buffer.append(s); + buffer.append("\n"); + } + contents = buffer.toString(); + } finally { + if (reader != null) + reader.close(); + } + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JarInstaller.java b/installer/src/java/org/python/util/install/JarInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JarInstaller.java @@ -0,0 +1,283 @@ +package org.python.util.install; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Working horse extracting the contents of the installation .jar to the file system.
+ * The directory stucture is preserved, but there is the possibility to exclude some entries + * (directories at the moment). + */ +public class JarInstaller { + + public static final String JYTHON_JAR = "jython.jar"; + + private static final String PATH_SEPARATOR = "/"; + + private static final String LIB_NAME_SEP = "Lib" + PATH_SEPARATOR; + + private static final String LIB_PAWT_SEP = LIB_NAME_SEP + "pawt" + PATH_SEPARATOR; + + private static final int BUFFER_SIZE = 1024; + + private ProgressListener _progressListener; + + private JarInfo _jarInfo; + + private List _installationListeners; + + public JarInstaller(ProgressListener progressListener, JarInfo jarInfo) { + _progressListener = progressListener; + _jarInfo = jarInfo; + _installationListeners = new ArrayList(); + } + + /** + * Do the pysical installation: + *

    + *
  • unzip the files + *
  • generate the start scripts + *
+ * + * @param targetDirectory + * @param installationType + */ + public void inflate(final File targetDirectory, InstallationType installationType, JavaHomeHandler javaHomeHandler) { + try { + // has to correspond with build.xml + // has to correspond with build.Lib.include.properties + List excludeDirs = _jarInfo.getExcludeDirs(); + List coreLibFiles = new ArrayList(); + if (!installationType.installSources()) { + excludeDirs.add("src"); + excludeDirs.add("grammar"); + excludeDirs.add("extlibs"); + } + if (!installationType.installDocumentation()) { + excludeDirs.add("Doc"); + } + if (!installationType.installDemosAndExamples()) { + excludeDirs.add("Demo"); + } + if (!installationType.installLibraryModules()) { + excludeDirs.add(LIB_NAME_SEP + "email"); + excludeDirs.add(LIB_NAME_SEP + "encodings"); + excludeDirs.add(LIB_NAME_SEP + "test"); + excludeDirs.add(LIB_NAME_SEP + "jxxload_help"); + coreLibFiles = getCoreLibFiles(); + } + if (installationType.isStandalone()) { + excludeDirs.add("Tools"); + excludeDirs.add(LIB_NAME_SEP + "email/test"); + excludeDirs.add(LIB_NAME_SEP + "test"); + } + int count = 0; + int percent = 0; + int numberOfIntervals = 100 / _progressListener.getInterval(); + int numberOfEntries = approximateNumberOfEntries(installationType); + int threshold = numberOfEntries / numberOfIntervals + 1; // +1 = pessimistic + boolean coreExclusionReported = false; + // unzip + ZipInputStream zipInput = new ZipInputStream(new BufferedInputStream(new FileInputStream(_jarInfo.getJarFile()), + BUFFER_SIZE)); + ZipEntry zipEntry = zipInput.getNextEntry(); + while (zipEntry != null) { + String zipEntryName = zipEntry.getName(); + boolean exclude = false; + // handle exclusion of directories + Iterator excludeDirsAsIterator = excludeDirs.iterator(); + while (excludeDirsAsIterator.hasNext()) { + if (zipEntryName.startsWith(excludeDirsAsIterator.next() + + PATH_SEPARATOR)) { + exclude = true; + } + } + // exclude build.xml when not installing source + if (!installationType.installSources() && zipEntryName.equals("build.xml")) + exclude = true; + // handle exclusion of core Lib files + if (!exclude) { + exclude = shouldExcludeFile(installationType, + coreLibFiles, + zipEntry, + zipEntryName); + if (Installation.isVerbose() && !coreExclusionReported && exclude) { + ConsoleInstaller.message("excluding some .py files, like " + zipEntryName); + coreExclusionReported = true; + } + } + if (exclude) { + if (Installation.isVerbose() && zipEntry.isDirectory()) { + ConsoleInstaller.message("excluding directory " + zipEntryName); + } + } else { + count++; + if (count % threshold == 0) { + percent = percent + _progressListener.getInterval(); + _progressListener.progressChanged(percent); + } + createDirectories(targetDirectory, zipEntryName); + if (!zipEntry.isDirectory()) { + File file = createFile(targetDirectory, zipEntryName); + _progressListener.progressEntry(file.getAbsolutePath()); + FileOutputStream output = new FileOutputStream(file); + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = zipInput.read(buffer)) > 0) { + output.write(buffer, 0, len); + } + output.close(); + file.setLastModified(zipEntry.getTime()); + } + } + zipInput.closeEntry(); + zipEntry = zipInput.getNextEntry(); + } + if (!installationType.isStandalone()) { + // generate start scripts + _progressListener.progressStartScripts(); + StartScriptGenerator generator = new StartScriptGenerator(targetDirectory, javaHomeHandler); + generator.generateStartScripts(); + } else { + _progressListener.progressStandalone(); + File jythonJar = new File(targetDirectory, JYTHON_JAR); + File jythonPlainJar = new File(targetDirectory, "plain_" + JYTHON_JAR); + jythonJar.renameTo(jythonPlainJar); + File libDir = new File(targetDirectory, "Lib"); + StandalonePackager packager = new StandalonePackager(jythonJar); + packager.addJarFile(jythonPlainJar); + _progressListener.progressChanged(90); // approx + packager.addFullDirectory(libDir); + packager.close(); + // TODO:Oti move to FileHelper + StandalonePackager.emptyDirectory(targetDirectory, jythonJar); + } + // finish: inform listeners + _progressListener.progressFinished(); + Iterator installationListenersIterator = _installationListeners.iterator(); + while (installationListenersIterator.hasNext()) { + installationListenersIterator.next().progressFinished(); + } + } catch (IOException ioe) { + throw new InstallerException(Installation.getText(TextKeys.ERROR_ACCESS_JARFILE), ioe); + } + } + + public void addInstallationListener(InstallationListener installationListener) { + if (installationListener != null) { + _installationListeners.add(installationListener); + } + } + + private int approximateNumberOfEntries(InstallationType installationType) { + int numberOfEntries = 200; // core (minimum) + if (installationType.installLibraryModules()) { + if (installationType.isStandalone()) { + numberOfEntries += 450; + } else { + numberOfEntries += 1300; + } + } + if (installationType.installDemosAndExamples()) { + numberOfEntries += 70; + } + if (installationType.installDocumentation()) { + numberOfEntries += 500; + } + if (installationType.installSources()) { + numberOfEntries += 1000; + } + return numberOfEntries; + } + + private void createDirectories(final File targetDirectory, final String zipEntryName) { + int lastSepIndex = zipEntryName.lastIndexOf(PATH_SEPARATOR); + if (lastSepIndex > 0) { + File directory = new File(targetDirectory, zipEntryName.substring(0, lastSepIndex)); + if (directory.exists() && directory.isDirectory()) {} else { + if (!directory.mkdirs()) { + throw new InstallerException(Installation.getText(TextKeys.UNABLE_CREATE_DIRECTORY, + directory.getAbsolutePath())); + } + } + } + } + + private File createFile(final File targetDirectory, final String zipEntryName) + throws IOException { + File file = new File(targetDirectory, zipEntryName); + if (file.exists() && file.isFile()) {} else { + if (!file.createNewFile()) { + throw new InstallerException(Installation.getText(TextKeys.UNABLE_CREATE_FILE, + file.getCanonicalPath())); + } + } + return file; + } + + private List getCoreLibFiles() { + List coreLibFiles = new ArrayList(); + coreLibFiles.add("__future__.py"); + coreLibFiles.add("copy.py"); + coreLibFiles.add("dbexts.py"); + coreLibFiles.add("imaplib.py"); + coreLibFiles.add("isql.py"); + coreLibFiles.add("javaos.py"); + coreLibFiles.add("javapath.py"); + coreLibFiles.add("jreload.py"); + coreLibFiles.add("marshal.py"); + coreLibFiles.add("ntpath.py"); + coreLibFiles.add("os.py"); + coreLibFiles.add("popen2.py"); + coreLibFiles.add("posixpath.py"); + coreLibFiles.add("random.py"); + coreLibFiles.add("re.py"); + coreLibFiles.add("site.py"); + coreLibFiles.add("socket.py"); + coreLibFiles.add("sre.py"); + coreLibFiles.add("sre_compile.py"); + coreLibFiles.add("sre_constants.py"); + coreLibFiles.add("sre_parse.py"); + coreLibFiles.add("stat.py"); + coreLibFiles.add("string.py"); + coreLibFiles.add("threading.py"); + coreLibFiles.add("UserDict.py"); + coreLibFiles.add("zipfile.py"); + coreLibFiles.add("zlib.py"); + return coreLibFiles; + } + + private boolean shouldExcludeFile(InstallationType installationType, + List coreLibFiles, + ZipEntry zipEntry, + String zipEntryName) { + boolean exclude = false; + if (!installationType.installLibraryModules()) { + // handle files in Lib + if (!zipEntry.isDirectory() && zipEntryName.startsWith(LIB_NAME_SEP)) { + // include all files in /pawt subdirectory + if (!zipEntryName.startsWith(LIB_PAWT_SEP)) { + if (zipEntryName.endsWith(".py")) { // only compare *.py files + exclude = true; + Iterator coreLibFilesAsIterator = coreLibFiles.iterator(); + while (coreLibFilesAsIterator.hasNext()) { + String coreFileName = coreLibFilesAsIterator.next(); + if (zipEntryName.endsWith(PATH_SEPARATOR + coreFileName)) { + exclude = false; + } + } + } + } + } + } + return exclude; + } +} diff --git a/installer/src/java/org/python/util/install/JavaHomeHandler.java b/installer/src/java/org/python/util/install/JavaHomeHandler.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaHomeHandler.java @@ -0,0 +1,209 @@ +package org.python.util.install; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * Unified entry point for treatment of java.home + *

+ * Note that the system property java.home is never changed + */ +public final class JavaHomeHandler { + + public static final String JAVA_HOME = "java.home"; + + private static final String JAVA = "java"; + + private static final String JAVA_EXE = "java.exe"; + + private static final String BIN = "bin"; + + private static final String DEFAULT = "_default_"; + + private static final String EMPTY = ""; + + /** + * A map for java home strings and their respective executable names + */ + private static Map _executableNames; + + /** + * The current java home + */ + private String _currentJavaHome; + + /** + * create a java home handler for the default java home + */ + public JavaHomeHandler() { + this(DEFAULT); + } + + /** + * create a java home handler for a java home deviation + * + * @param currentJavaHome + * The deviated java home + */ + public JavaHomeHandler(String currentJavaHome) { + setCurrentJavaHome(currentJavaHome); + check(getCurrentJavaHome()); + } + + /** + * get the name of the java executable + * + * @return A name of a java executable which can be passed to {@link ChildProcess} + */ + public String getExecutableName() { + return getExecutableName(getCurrentJavaHome()); + } + + /** + * tell the validity of the current java home + *

+ * Note: if the current java home is not valid, {@link JavaHomeHandler#getExecutableName()} + * still returns the name of a callable java + * + * @return true if we have a valid java home, false otherwise. + */ + public boolean isValidHome() { + return !getFallbackExecutableName().equals(getExecutableName()); + } + + /** + * get the current java home, if it is valid + * + * @return The current java home + * @throws InstallerException + * if there is no valid java home + * + * @see JavaHomeHandler#isValidHome() + */ + public File getHome() throws InstallerException { + if (!isValidHome()) { + throw new InstallerException("no valid java home"); + } else { + return new File(getCurrentJavaHome()); + } + } + + /** + * @return true if the current java home is a deviation, false + * otherwise + */ + public boolean isDeviation() { + // make sure the default java home is also known + if (!getExecutableNames().containsKey(DEFAULT)) { + check(DEFAULT); + } + return !getExecutableName(DEFAULT).equals(getExecutableName(getCurrentJavaHome())); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(80); + builder.append("["); + if(!isValidHome()) { + builder.append("in"); + } + builder.append("valid java home: "); + builder.append(getCurrentJavaHome()); + builder.append("; executable: "); + builder.append(getExecutableName()); + builder.append("]"); + return builder.toString(); + } + + /** + * reset the handler (clear all stored java homes) + */ + static void reset() { + getExecutableNames().clear(); + } + + private String getExecutableName(String javaHome) { + Map executableNames = getExecutableNames(); + if (!executableNames.containsKey(javaHome)) { + check(javaHome); + } + return executableNames.get(javaHome); + } + + private void check(String javaHome) { + boolean valid = false; + boolean isDefault = false; + File javaExecutableFile = null; + if (DEFAULT.equals(javaHome)) { + isDefault = true; + javaHome = System.getProperty(JAVA_HOME, EMPTY); + } + if (javaHome.length() > 0) { + File javaHomeDir = new File(javaHome); + if (javaHomeDir.exists() && javaHomeDir.isDirectory()) { + File binDir = new File(javaHomeDir, BIN); + if (binDir.exists() && binDir.isDirectory()) { + javaExecutableFile = getExecutableFile(binDir); + if (javaExecutableFile.exists()) { + valid = true; + } + } + } + } + if (valid) { + addExecutable(javaHome, javaExecutableFile); + if (isDefault) { + addExecutable(DEFAULT, javaExecutableFile); + if (DEFAULT.equals(getCurrentJavaHome())) { + // update the current home to the real one + setCurrentJavaHome(javaHome); + } + } + } else { + addFallbackExecutable(javaHome); + if (isDefault) { + addFallbackExecutable(DEFAULT); + } + } + } + + private String getFallbackExecutableName() { + return JAVA; + } + + private void addFallbackExecutable(String javaHome) { + getExecutableNames().put(javaHome, getFallbackExecutableName()); + } + + private void addExecutable(String javaHome, File javaExecutableFile) { + getExecutableNames().put(javaHome, javaExecutableFile.getAbsolutePath()); + } + + private File getExecutableFile(File binDir) { + if (Installation.isWindows()) { + return new File(binDir, JAVA_EXE); + } else { + return new File(binDir, JAVA); + } + } + + private static Map getExecutableNames() { + if (_executableNames == null) { + _executableNames = new HashMap(); + } + return _executableNames; + } + + private String getCurrentJavaHome() { + if (_currentJavaHome == null) { + return DEFAULT; + } else { + return _currentJavaHome; + } + } + + private void setCurrentJavaHome(String currentJavaHome) { + _currentJavaHome = currentJavaHome.trim(); + } +} diff --git a/installer/src/java/org/python/util/install/JavaSelectionPage.java b/installer/src/java/org/python/util/install/JavaSelectionPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaSelectionPage.java @@ -0,0 +1,199 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; + +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +public class JavaSelectionPage extends AbstractWizardPage { + + private static final long serialVersionUID = 2871052924519223110L; + + private final static String _CURRENT_ACTION_COMMAND = "current"; + private final static String _OTHER_ACTION_COMMAND = "other"; + + private JRadioButton _currentButton; + private JRadioButton _otherButton; + + private JLabel _label; + private JTextField _javaHome; + private JButton _browse; + + public JavaSelectionPage() { + super(); + initComponents(); + } + + private void initComponents() { + // label for java home + _label = new JLabel(); + + // radio buttons + RadioButtonListener radioButtonListener = new RadioButtonListener(); + _currentButton = new JRadioButton(); + _currentButton.setActionCommand(_CURRENT_ACTION_COMMAND); + _currentButton.addActionListener(radioButtonListener); + _otherButton = new JRadioButton(); + _otherButton.setActionCommand(_OTHER_ACTION_COMMAND); + _otherButton.addActionListener(radioButtonListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_currentButton); + radioButtonGroup.add(_otherButton); + JPanel radioPanel = new JPanel(new GridLayout(0, 1)); + radioPanel.add(_currentButton); + radioPanel.add(_otherButton); + + // directory for java home + _javaHome = new JTextField(40); + _javaHome.addFocusListener(new JavaFocusListener()); + // browse button + _browse = new JButton(); + _browse.addActionListener(new BrowseButtonListener()); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(radioPanel, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panel.add(_javaHome, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panel.add(_browse, gridBagConstraints); + + add(panel); + } + + JTextField getJavaHome() { + return _javaHome; + } + + protected String getTitle() { + return getText(TARGET_JAVA_HOME_PROPERTY); + } + + protected String getDescription() { + return getText(CHOOSE_JRE); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _currentButton; + } + + protected void activate() { + _label.setText(getText(SELECT_JAVA_HOME) + ": "); + _currentButton.setText(getText(CURRENT)); + _otherButton.setText(getText(OTHER)); + _browse.setText(getText(BROWSE)); + setValues(); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private void setValues() { + boolean current = true; + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + if (javaHomeHandler.isDeviation()) { + current = false; + } + setCurrent(current); + } + + private void setCurrent(boolean current) { + if (current) { + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler()); + _currentButton.setSelected(true); + _otherButton.setSelected(false); + _javaHome.setEnabled(false); + _browse.setEnabled(false); + } else { + _currentButton.setSelected(false); + _otherButton.setSelected(true); + _javaHome.setEnabled(true); + _browse.setEnabled(true); + } + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + if (javaHomeHandler.isValidHome()) { + _javaHome.setText(javaHomeHandler.getHome().getAbsolutePath()); + } else { + _javaHome.setText(""); + } + _javaHome.setToolTipText(_javaHome.getText()); + } + + private final class BrowseButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(new File(_javaHome.getText())); + fileChooser.setDialogTitle(getText(SELECT_JAVA_HOME)); + // the filter is at the moment only used for the title of the dialog: + fileChooser.setFileFilter(new DirectoryFilter()); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (fileChooser.isAcceptAllFileFilterUsed()) { + if (Installation.isMacintosh() && Installation.isJDK141()) { + // work around ArrayIndexOutOfBoundsExceptio on Mac OS X, java version 1.4.1 + } else { + fileChooser.setAcceptAllFileFilterUsed(false); + } + } + int returnValue = fileChooser.showDialog(_browse, getText(SELECT)); + if (returnValue == JFileChooser.APPROVE_OPTION) { + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler(fileChooser.getSelectedFile().getAbsolutePath())); + setValues(); + } + } + } + + private final class RadioButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + setCurrent(_CURRENT_ACTION_COMMAND.equals(actionCommand)); + } + } + + private final class JavaFocusListener implements FocusListener { + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + String javaHome = _javaHome.getText(); + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler(javaHome)); + _javaHome.setToolTipText(javaHome); + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java b/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java @@ -0,0 +1,31 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +public class JavaSelectionPageValidator extends AbstractWizardValidator { + + JavaSelectionPage _page; + + JavaSelectionPageValidator(JavaSelectionPage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + String directory = _page.getJavaHome().getText().trim(); // trim to be sure + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(directory); + if(javaHomeHandler.isDeviation()) { + javaVersionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (javaVersionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + throw new ValidationException(javaVersionInfo.getReason()); + } + } else { + // no experiments if current java is selected + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); + } + FrameInstaller.setJavaHomeHandler(javaHomeHandler); + FrameInstaller.setJavaVersionInfo(javaVersionInfo); + } + +} diff --git a/installer/src/java/org/python/util/install/JavaVersionTester.java b/installer/src/java/org/python/util/install/JavaVersionTester.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaVersionTester.java @@ -0,0 +1,61 @@ +package org.python.util.install; + +import java.io.File; + +/** + * Helper class to test a java version + */ +public class JavaVersionTester { + + public static final String JAVA_HOME = "java.home"; + protected static final String JAVA_VERSION = "java.version"; + public static final String JAVA_SPECIFICATION_VERSION = "java.specification.version"; + protected static final String JAVA_VENDOR = "java.vendor"; + private static final String NEWLINE = "\n"; + + private static final String UNKNOWN = ""; + + public static void main(String[] args) { + if (args.length > 0) { + String tempFilePath = args[0]; + File tempFile = new File(tempFilePath); + if (tempFile.exists() && tempFile.canWrite()) { + try { + writeTempFile(tempFile); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } else { + if (!tempFile.exists()) { + System.err.println("temp file " + tempFilePath + " does not exist"); + } else { + System.err.println("cannot write to temp file " + tempFilePath); + } + System.exit(1); + } + } else { + System.err.println("no temp file given. usage: JavaVersionTester tempfile"); + System.out.println("exiting with 1"); + System.exit(1); + } + } + + private static void writeTempFile(File file) throws Exception { + FileHelper.write(file, createFileContent()); + } + + private static String createFileContent() { + StringBuffer sb = new StringBuffer(500); + String java_home = new JavaHomeHandler().getExecutableName(); + if (File.separatorChar != '/') { + java_home = java_home.replace(File.separatorChar, '/'); // backslash would be interpreted as escape char + } + sb.append(JAVA_HOME + "=" + java_home + NEWLINE); + sb.append(JAVA_VERSION + "=" + System.getProperty(JAVA_VERSION, UNKNOWN) + NEWLINE); + sb.append(JAVA_SPECIFICATION_VERSION + "=" + System.getProperty(JAVA_SPECIFICATION_VERSION, UNKNOWN) + NEWLINE); + sb.append(JAVA_VENDOR + "=" + System.getProperty(JAVA_VENDOR, UNKNOWN) + NEWLINE); + return sb.toString(); + } + +} diff --git a/installer/src/java/org/python/util/install/LanguagePage.java b/installer/src/java/org/python/util/install/LanguagePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LanguagePage.java @@ -0,0 +1,121 @@ +package org.python.util.install; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; + +public class LanguagePage extends AbstractWizardPage { + + private static Map _languageIndexes = new HashMap(2); + static { + _languageIndexes.put(Locale.ENGLISH, new Integer(0)); + _languageIndexes.put(Locale.GERMAN, new Integer(1)); + } + + private JLabel _label; + private JComboBox _languageBox; + private JarInfo _jarInfo; + private boolean _activated; + private boolean _stopListening; + + public LanguagePage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + _activated = false; + _stopListening = false; + initComponents(); + } + + private void initComponents() { + _label = new JLabel(); + add(_label); + _languageBox = new JComboBox(); + _languageBox.addActionListener(new LanguageBoxListener()); + add(_languageBox); + } + + protected String getTitle() { + return getText(WELCOME_TO_JYTHON); + } + + protected String getDescription() { + return getText(VERSION_INFO, _jarInfo.getVersion()); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _languageBox; + } + + protected void activate() { + _label.setText(getText(SELECT_LANGUAGE) + ": "); + // replace combo box items (localization) + int itemCount = _languageBox.getItemCount(); + _stopListening = true; // adding and removing fires an action event + for (int i = 0; i < itemCount; i++) { + _languageBox.removeItemAt(0); + } + _languageBox.addItem(getText(ENGLISH)); // keep indexes here + _languageBox.addItem(getText(GERMAN)); + _stopListening = false; + if (!_activated) { + // preselect German if default looks like German + if (Locale.getDefault().toString().startsWith(Locale.GERMAN.toString())) { + _languageBox.setSelectedIndex(getLanguageIndex(Locale.GERMAN)); + FrameInstaller.setLanguage(Locale.GERMAN); + } + } else { + _languageBox.setSelectedIndex(getLanguageIndex(FrameInstaller.getLanguage())); + } + _activated = true; + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private int getLanguageIndex(Locale locale) { + return ((Integer) _languageIndexes.get(locale)).intValue(); + } + + private Locale getLanguageFromIndex(int index) { + Integer indexInteger = new Integer(index); + Iterator languages = _languageIndexes.entrySet().iterator(); + while (languages.hasNext()) { + Map.Entry entry = (Map.Entry) languages.next(); + if (entry.getValue().equals(indexInteger)) { + return (Locale) entry.getKey(); + } + } + return null; + } + + private class LanguageBoxListener implements ActionListener { + public void actionPerformed(ActionEvent ae) { + if (!_stopListening) { + FrameInstaller.setLanguage(getLanguageFromIndex(_languageBox.getSelectedIndex())); + } + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/LicensePage.java b/installer/src/java/org/python/util/install/LicensePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LicensePage.java @@ -0,0 +1,120 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class LicensePage extends AbstractWizardPage { + + private static final String _ACCEPT_ACTION_COMMAND = "1"; + private static final String _DO_NOT_ACCEPT_ACTION_COMMAND = "0"; + + private JRadioButton _acceptButton; + private JRadioButton _doNotAcceptButton; + + public LicensePage(JarInfo jarInfo) { + super(); + initComponents(jarInfo); + } + + private void initComponents(JarInfo jarInfo) { + String licenseText = "n/a"; + try { + licenseText = jarInfo.getLicenseText(); + } catch (IOException e) { + e.printStackTrace(); + } + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new GridLayout(1, 1, 10, 10)); + JTextArea textArea = new JTextArea(13, 80); + JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + textArea.setEditable(false); + textArea.setText(licenseText); + centerPanel.add(scrollPane); + + // radio buttons + JPanel southPanel = new JPanel(); + RadioButtonListener radioButtonListener = new RadioButtonListener(); + _acceptButton = new JRadioButton(); + _acceptButton.setActionCommand(_ACCEPT_ACTION_COMMAND); + _acceptButton.addActionListener(radioButtonListener); + _doNotAcceptButton = new JRadioButton(); + _doNotAcceptButton.setActionCommand(_DO_NOT_ACCEPT_ACTION_COMMAND); + _doNotAcceptButton.addActionListener(radioButtonListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_acceptButton); + radioButtonGroup.add(_doNotAcceptButton); + JPanel radioPanel = new JPanel(new GridLayout(1, 0)); + radioPanel.add(_acceptButton); + radioPanel.add(_doNotAcceptButton); + southPanel.add(radioPanel); + + setLayout(new BorderLayout(0, 5)); + add(centerPanel, BorderLayout.CENTER); + add(southPanel, BorderLayout.SOUTH); + } + + protected String getTitle() { + return getText(LICENSE); + } + + protected String getDescription() { + return getText(PLEASE_READ_LICENSE); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _doNotAcceptButton; + } + + protected void activate() { + _acceptButton.setText(getText(ACCEPT)); + _doNotAcceptButton.setText(getText(DO_NOT_ACCEPT)); + boolean accept = FrameInstaller.isAccept(); + _acceptButton.setSelected(accept); + _doNotAcceptButton.setSelected(!accept); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + protected boolean isAccept() { + return _acceptButton.isSelected() && !_doNotAcceptButton.isSelected(); + } + + private final static class RadioButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + if (actionCommand.equals(_ACCEPT_ACTION_COMMAND)) { + FrameInstaller.setAccept(true); + } else { + FrameInstaller.setAccept(false); + } + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/LicensePageValidator.java b/installer/src/java/org/python/util/install/LicensePageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LicensePageValidator.java @@ -0,0 +1,17 @@ +package org.python.util.install; + +public class LicensePageValidator extends AbstractWizardValidator { + + LicensePage _page; + + LicensePageValidator(LicensePage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException { + if (!_page.isAccept()) { + throw new ValidationException(getText(PLEASE_ACCEPT_LICENSE)); + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/OverviewPage.java b/installer/src/java/org/python/util/install/OverviewPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/OverviewPage.java @@ -0,0 +1,243 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; + +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.python.util.install.Installation.JavaVersionInfo; + +public class OverviewPage extends AbstractWizardPage { + + private final static int _LONGER_LENGTH = 25; + private final static int _SHORTER_LENGTH = 10; + + private JLabel _directoryLabel; + private JLabel _typeLabel; + private JTextField _directory; + private JTextField _type; + private JLabel _message; + + private JLabel _osLabel; + private JCheckBox _osBox; + private JLabel _javaLabel; + private JTextField _javaVendor; + private JTextField _javaVersion; + private JCheckBox _javaBox; + + public OverviewPage() { + super(); + initComponents(); + } + + private void initComponents() { + _directoryLabel = new JLabel(); + _directory = new JTextField(_LONGER_LENGTH); + _directory.setEditable(false); + _directory.setFocusable(false); + _typeLabel = new JLabel(); + _type = new JTextField(_LONGER_LENGTH); + _type.setEditable(false); + _type.setFocusable(false); + + _osLabel = new JLabel(); + JTextField osName = new JTextField(_LONGER_LENGTH); + osName.setText(System.getProperty(Installation.OS_NAME)); + osName.setToolTipText(System.getProperty(Installation.OS_NAME)); + osName.setEditable(false); + osName.setFocusable(false); + JTextField osVersion = new JTextField(_SHORTER_LENGTH); + osVersion.setText(System.getProperty(Installation.OS_VERSION)); + osVersion.setToolTipText(System.getProperty(Installation.OS_VERSION)); + osVersion.setEditable(false); + osVersion.setFocusable(false); + _osBox = new JCheckBox(); + _osBox.setEnabled(false); + _osBox.setSelected(Installation.isValidOs()); + _osBox.setFocusable(false); + + _javaLabel = new JLabel(); + _javaLabel.setFocusable(false); + _javaVendor = new JTextField(_LONGER_LENGTH); + _javaVendor.setEditable(false); + _javaVendor.setFocusable(false); + _javaVersion = new JTextField(_SHORTER_LENGTH); + _javaVersion.setEditable(false); + _javaVersion.setFocusable(false); + _javaBox = new JCheckBox(); + _javaBox.setEnabled(false); + _javaBox.setFocusable(false); + + _message = new JLabel(); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_directoryLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panel.add(_directory, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(_typeLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(_type, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panel.add(_osLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panel.add(osName, gridBagConstraints); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 2; + panel.add(osVersion, gridBagConstraints); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 2; + panel.add(_osBox, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + panel.add(_javaLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + panel.add(_javaVendor, gridBagConstraints); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 3; + panel.add(_javaVersion, gridBagConstraints); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 3; + panel.add(_javaBox, gridBagConstraints); + + // attn special constraints for message (should always be on last row) + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.gridwidth = 2; + panel.add(_message, gridBagConstraints); + + add(panel); + } + + protected String getTitle() { + return getText(OVERVIEW_TITLE); + } + + protected String getDescription() { + return getText(OVERVIEW_DESCRIPTION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + // directory + _directoryLabel.setText(getText(TARGET_DIRECTORY_PROPERTY) + ": "); + _directory.setText(FrameInstaller.getTargetDirectory()); + _directory.setToolTipText(FrameInstaller.getTargetDirectory()); + + // type + _typeLabel.setText(getText(INSTALLATION_TYPE) + ": "); + InstallationType installationType = FrameInstaller.getInstallationType(); + String typeText; + if (installationType.isAll()) { + typeText = getText(ALL); + } else if (installationType.isStandard()) { + typeText = getText(STANDARD); + } else if (installationType.isMinimum()) { + typeText = getText(MINIMUM); + } else if (installationType.isStandalone()) { + typeText = getText(STANDALONE); + } else { + typeText = getText(CUSTOM); + typeText += " ("; + boolean predecessor = false; + if (installationType.installLibraryModules()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES; + predecessor = true; + } + if (installationType.installDemosAndExamples()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES; + predecessor = true; + } + if (installationType.installDocumentation()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_DOCUMENTATION; + predecessor = true; + } + if (installationType.installSources()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_SOURCES; + predecessor = true; + } + typeText += ")"; + } + _type.setText(typeText); + _type.setToolTipText(typeText); + + // os + _osLabel.setText(getText(OS_INFO) + ": "); + String osText; + if (_osBox.isSelected()) { + osText = getText(OK); + } else { + osText = getText(MAYBE_NOT_SUPPORTED); + } + _osBox.setText(osText); + + // java + _javaLabel.setText(getText(JAVA_INFO) + ": "); + JavaVersionInfo javaVersionInfo = FrameInstaller.getJavaVersionInfo(); + _javaVendor.setText(javaVersionInfo.getVendor()); + _javaVendor.setToolTipText(javaVersionInfo.getVendor()); + _javaVersion.setText(javaVersionInfo.getVersion()); + _javaVersion.setToolTipText(javaVersionInfo.getVersion()); + _javaBox.setSelected(Installation.isValidJava(javaVersionInfo)); + String javaText; + if (_javaBox.isSelected()) { + javaText = getText(OK); + } else { + javaText = getText(NOT_OK); + } + _javaBox.setText(javaText); + + // message + _message.setText(getText(CONFIRM_START, getText(NEXT))); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ProgressListener.java b/installer/src/java/org/python/util/install/ProgressListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ProgressListener.java @@ -0,0 +1,15 @@ +package org.python.util.install; + +public interface ProgressListener extends InstallationListener { + + public int getInterval(); + + public void progressChanged(int newPercentage); + + public void progressEntry(String entry); + + public void progressStartScripts(); + + public void progressStandalone(); + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ProgressPage.java b/installer/src/java/org/python/util/install/ProgressPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ProgressPage.java @@ -0,0 +1,122 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.io.File; +import java.io.IOException; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +import org.python.util.install.driver.Autotest; + +public class ProgressPage extends AbstractWizardPage implements ProgressListener { + + private static final long serialVersionUID = 9013748834030994976L; + + private JarInfo _jarInfo; + private JLabel _label; + private JProgressBar _progressBar; + private JLabel _progressEntry; + private Autotest _autotest; + + public ProgressPage(JarInfo jarInfo, Autotest autotest) { + super(); + _jarInfo = jarInfo; + _autotest = autotest; + initComponents(); + } + + private void initComponents() { + JPanel northPanel = new JPanel(); + _label = new JLabel(); + northPanel.add(_label); + _progressBar = new JProgressBar(); + northPanel.add(_progressBar); + JPanel centerPanel = new JPanel(); + _progressEntry = new JLabel(); + centerPanel.add(_progressEntry); + setLayout(new BorderLayout(0, 5)); + add(northPanel, BorderLayout.NORTH); + add(centerPanel, BorderLayout.CENTER); + } + + protected String getTitle() { + return getText(INSTALLATION_IN_PROGRESS); + } + + protected String getDescription() { + return getText(PLEASE_WAIT); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return false; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + _label.setText(getText(PROGRESS) + ": "); + _progressBar.setValue(0); + _progressBar.setStringPainted(true); + try { + _progressEntry.setText(getText(INFLATING, _jarInfo.getJarFile().getName())); + } catch (IOException e) { + // should not happen + } + JarInstaller jarInstaller = new JarInstaller(this, _jarInfo); + if (_autotest != null) { + jarInstaller.addInstallationListener(_autotest); + } + File targetDirectory = new File(FrameInstaller.getTargetDirectory()); + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + jarInstaller.inflate(targetDirectory, FrameInstaller.getInstallationType(), javaHomeHandler); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + // + // interface ProgressListener + // + + public void progressChanged(int newPercentage) { + _progressBar.setValue(newPercentage); + } + + public int getInterval() { + return 1; + } + + public void progressFinished() { + _progressBar.setValue(100); + getWizard().gotoNextPage(); + } + + public void progressEntry(String entry) { + _progressEntry.setText(getText(INFLATING, entry)); + } + + public void progressStartScripts() { + _progressEntry.setText(getText(GENERATING_START_SCRIPTS)); + } + + public void progressStandalone() { + _progressEntry.setText(getText(PACKING_STANDALONE_JAR)); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ReadmePage.java b/installer/src/java/org/python/util/install/ReadmePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ReadmePage.java @@ -0,0 +1,75 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.io.IOException; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class ReadmePage extends AbstractWizardPage { + + private JTextArea _textArea; + private JarInfo _jarInfo; + + public ReadmePage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new GridLayout(1, 1)); + _textArea = new JTextArea(13, 80); + JScrollPane scrollPane = new JScrollPane(_textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + _textArea.setEditable(false); + _textArea.setText("n/a"); + centerPanel.add(scrollPane); + + setLayout(new BorderLayout(0, 5)); + add(centerPanel, BorderLayout.CENTER); + } + + protected String getTitle() { + return getText(README); + } + + protected String getDescription() { + return getText(PLEASE_README); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + try { + _textArea.setText(_jarInfo.getReadmeText()); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/StandalonePackager.java b/installer/src/java/org/python/util/install/StandalonePackager.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/StandalonePackager.java @@ -0,0 +1,184 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +public class StandalonePackager { + + private final static int BUF_SIZE = 1024; + + private File _jarFile; + private Manifest _manifest; + private JarOutputStream _jarOut; + + /** + * Helper class to pack stuff into a single .jar file. + *

+ * If a .jar file has to be added, this should be the first add (because of the MANIFEST). + */ + public StandalonePackager(File jarFile) { + _jarFile = jarFile; + } + + /** + * add a file, in given parent dir (null = top) + * + * @param file to write to jar + * @param parentDir as String + * + * @throws IOException + */ + public void addFile(File file, String parentDir) throws IOException { + byte[] buffer = new byte[BUF_SIZE]; + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + String jarEntryName = null; + if (parentDir != null && parentDir.length() > 0) { + jarEntryName = parentDir + "/" + file.getName(); + } else { + jarEntryName = file.getName(); + } + getJarOutputStream().putNextEntry(new JarEntry(jarEntryName)); + for (int read = 0; read != -1; read = inputStream.read(buffer)) { + getJarOutputStream().write(buffer, 0, read); + } + getJarOutputStream().closeEntry(); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + /** + * add a full directory + * + * @param directory + * @throws IOException + */ + public void addFullDirectory(File directory) throws IOException { + addDirectory(directory, null); + } + + /** + * add the contents of a given jar file + * + * @param jarFile to add + * @throws IOException + * @throws FileNotFoundException + */ + public void addJarFile(File jarFile) throws FileNotFoundException, IOException { + JarFile jarJarFile = new JarFile(jarFile); + try { + _manifest = jarJarFile.getManifest(); + } finally { + jarJarFile.close(); + } + + JarInputStream inputStr = null; + try { + inputStr = new JarInputStream(new FileInputStream(jarFile)); + JarEntry entry = inputStr.getNextJarEntry(); + while (entry != null) { + getJarOutputStream().putNextEntry(entry); + byte[] buffer = new byte[BUF_SIZE]; + int len; + while ((len = inputStr.read(buffer)) > 0) { + getJarOutputStream().write(buffer, 0, len); + } + getJarOutputStream().closeEntry(); + entry = inputStr.getNextJarEntry(); + } + } finally { + if (inputStr != null) { + try { + inputStr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public void close() throws FileNotFoundException, IOException { + getJarOutputStream().close(); + } + + /** + * removes all files in directory dir (and subdirectories), except excludeFile. + * + * @param dir + * @param excludeFile + */ + public static void emptyDirectory(File dir, File excludeFile) { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + if (!files[i].equals(excludeFile)) { + if (files[i].isDirectory()) { + emptyDirectory(files[i], excludeFile); + } else { + files[i].delete(); + } + } + } + if (dir.listFiles().length == 0) { + dir.delete(); + } + } + + /** + * add the given directory to the jar, including all subdirectories, in given parent dir (null = + * top) + * + * @param dir to add to the jar + * @param parentDir to save in jar + * @throws IOException + */ + private void addDirectory(File dir, String parentDir) throws IOException { + if (!dir.isDirectory()) + return; + File[] filesInDir = dir.listFiles(); + for (int i = 0; i < filesInDir.length; i++) { + File currentFile = filesInDir[i]; + if (currentFile.isFile()) { + if (parentDir != null && parentDir.length() > 0) { + addFile(currentFile, parentDir + "/" + dir.getName()); + } else { + addFile(currentFile, dir.getName()); + } + } else { + String newParentDir = null; + if (parentDir != null && parentDir.length() > 0) { + newParentDir = parentDir + "/" + dir.getName(); + } else { + newParentDir = dir.getName(); + } + addDirectory(currentFile, newParentDir); + } + } + } + + /** + * @return the jar output stream + */ + private JarOutputStream getJarOutputStream() throws FileNotFoundException, IOException { + if (_jarOut == null) { + if (_manifest != null) { + _jarOut = new JarOutputStream(new FileOutputStream(_jarFile), _manifest); + } else { + _jarOut = new JarOutputStream(new FileOutputStream(_jarFile)); + } + } + return _jarOut; + } + +} diff --git a/installer/src/java/org/python/util/install/StartScriptGenerator.java b/installer/src/java/org/python/util/install/StartScriptGenerator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/StartScriptGenerator.java @@ -0,0 +1,240 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Date; + +public class StartScriptGenerator { + + protected final static int UNIX_FLAVOUR = 10; + + protected final static int WINDOWS_FLAVOUR = 30; + + protected final static int BOTH_FLAVOUR = 50; + + protected final static String WIN_CR_LF; + + private final static String JAVA_HOME = "JAVA_HOME"; + + /** do not hard-wire JYTHON_HOME */ + private final static String JYTHON_HOME_FALLBACK = "JYTHON_HOME_FALLBACK"; + + private final static String JYTHON = "jython"; + + private final static String JYTHON_BAT = "jython.bat"; + static { + int dInt = Integer.parseInt("0d", 16); + int aInt = Integer.parseInt("0a", 16); + WIN_CR_LF = new String(new char[] {(char)dInt, (char)aInt}); + } + + private File _targetDirectory; + + private JavaHomeHandler _javaHomeHandler; + + private int _flavour; + + public StartScriptGenerator(File targetDirectory, JavaHomeHandler javaHomeHandler) { + _targetDirectory = targetDirectory; + _javaHomeHandler = javaHomeHandler; + if (Installation.isWindows()) { + setFlavour(WINDOWS_FLAVOUR); + } else { + // everything else defaults to unix at the moment + setFlavour(UNIX_FLAVOUR); + } + } + + protected void setFlavour(int flavour) { + _flavour = flavour; + if (flavour == WINDOWS_FLAVOUR) { + // check if we should create unix like scripts, too + if (hasUnixlikeShell()) { + _flavour = BOTH_FLAVOUR; + } + } + } + + protected int getFlavour() { + return _flavour; + } + + protected boolean hasUnixlikeShell() { + int errorCode = 0; + try { + String command[] = new String[] {"sh", "-c", "env"}; + long timeout = 3000; + ChildProcess childProcess = new ChildProcess(command, timeout); + childProcess.setDebug(false); + childProcess.setSilent(true); + errorCode = childProcess.run(); + } catch (Throwable t) { + errorCode = 1; + } + return errorCode == 0; + } + + protected final void generateStartScripts() throws IOException { + File bin = new File(getTargetDirectory(), "bin"); + File bin_jython = new File(bin, JYTHON); + switch(getFlavour()){ + case BOTH_FLAVOUR: + writeToTargetDir(JYTHON_BAT, getJythonScript(WINDOWS_FLAVOUR)); + FileHelper.makeExecutable(writeToTargetDir(JYTHON, getJythonScript(BOTH_FLAVOUR))); + FileHelper.makeExecutable(bin_jython); + break; + case WINDOWS_FLAVOUR: + writeToTargetDir(JYTHON_BAT, getJythonScript(WINDOWS_FLAVOUR)); + // delete the *nix script in /bin dir + bin_jython.delete(); + break; + default: + FileHelper.makeExecutable(writeToTargetDir(JYTHON, getJythonScript(UNIX_FLAVOUR))); + FileHelper.makeExecutable(bin_jython); + // delete the windows script in /bin dir + File bin_jython_bat = new File(bin, JYTHON_BAT); + bin_jython_bat.delete(); + break; + } + } + + /** + * only protected for unit test use + */ + protected final String getJythonScript(int flavour) throws IOException { + if (flavour == WINDOWS_FLAVOUR) { + return getStartScript(getWindowsJythonTemplate()) + readFromFile(JYTHON_BAT); + } else { + return getStartScript(getUnixJythonTemplate()) + readFromFile(JYTHON); + } + } + + /** + * These placeholders are valid for all private methods: + * + * {0} : current date
+ * {1} : user.name
+ * {2} : target directory
+ */ + private String getStartScript(String template) throws IOException { + String parameters[] = new String[4]; + parameters[0] = new Date().toString(); + parameters[1] = System.getProperty("user.name"); + parameters[2] = getTargetDirectory().getCanonicalPath(); + return MessageFormat.format(template, (Object[])parameters); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private String getWindowsJythonTemplate() { + StringBuilder builder = getWindowsHeaderTemplate(); + builder.append("set "); + builder.append(JAVA_HOME); + builder.append("="); + if (_javaHomeHandler.isValidHome()) { + builder.append("\""); + builder.append(_javaHomeHandler.getHome().getAbsolutePath()); + builder.append("\""); + } + builder.append(WIN_CR_LF); + builder.append("set "); + builder.append(JYTHON_HOME_FALLBACK); + builder.append("=\"{2}\""); + builder.append(WIN_CR_LF); + builder.append(WIN_CR_LF); + return builder.toString(); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private StringBuilder getWindowsHeaderTemplate() { + StringBuilder builder = new StringBuilder(1000); + builder.append("@echo off"); + builder.append(WIN_CR_LF); + builder.append("rem This file was generated by the Jython installer"); + builder.append(WIN_CR_LF); + builder.append("rem Created on {0} by {1}"); + builder.append(WIN_CR_LF); + builder.append(WIN_CR_LF); + return builder; + } + + /** + * placeholders: + * + * @see getStartScript + */ + private String getUnixJythonTemplate() { + StringBuilder builder = getUnixHeaderTemplate(); + builder.append(JAVA_HOME); + builder.append("="); + if (_javaHomeHandler.isValidHome()) { + builder.append("\""); + builder.append(_javaHomeHandler.getHome().getAbsolutePath()); + builder.append("\""); + } + builder.append("\n"); + builder.append(JYTHON_HOME_FALLBACK); + builder.append("=\"{2}\"\n"); + builder.append("\n"); + return builder.toString(); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private StringBuilder getUnixHeaderTemplate() { + StringBuilder builder = new StringBuilder(1000); + builder.append("#!/usr/bin/env bash\n"); + builder.append("\n"); + builder.append("# This file was generated by the Jython installer\n"); + builder.append("# Created on {0} by {1}\n"); + builder.append("\n"); + return builder; + } + + /** + * @param fileName + * The short file name, e.g. JYTHON_BAT + * + * @throws IOException + */ + private String readFromFile(String fileName) throws IOException { + // default runtime location + File targetDirectory = getTargetDirectory(); + File file = new File(new File(targetDirectory, "bin"), fileName); + if (!file.exists()) { + // deviation: test time location + file = new File(targetDirectory, fileName); + } + return FileHelper.readAll(file); + } + + /** + * Create (or overwrite) the specified file in the target directory + * + * @param fileName + * The short file name, e.g. JYTHON_BAT + * @param contents + * + * @throws IOException + */ + private File writeToTargetDir(String fileName, String contents) throws IOException { + File file = new File(getTargetDirectory(), fileName); + FileHelper.write(file, contents); + return file; + } + + private File getTargetDirectory() { + return _targetDirectory; + } +} diff --git a/installer/src/java/org/python/util/install/SuccessPage.java b/installer/src/java/org/python/util/install/SuccessPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/SuccessPage.java @@ -0,0 +1,55 @@ +package org.python.util.install; + +import javax.swing.JComponent; +import javax.swing.JLabel; + +public class SuccessPage extends AbstractWizardPage { + + private JarInfo _jarInfo; + private JLabel _label; + + public SuccessPage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + _label = new JLabel(); + add(_label); + } + + protected String getTitle() { + return getText(CONGRATULATIONS); + } + + protected String getDescription() { + return getText(SUCCESS, _jarInfo.getVersion(), FrameInstaller.getTargetDirectory()); + } + + protected boolean isCancelVisible() { + return false; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + _label.setText(getText(PRESS_FINISH, getWizard().getFinishString())); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants.java b/installer/src/java/org/python/util/install/TextConstants.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants.java @@ -0,0 +1,148 @@ +package org.python.util.install; + +import java.util.ListResourceBundle; + +public class TextConstants extends ListResourceBundle implements TextKeys { + + static final Object[][] contents = { + // LOCALIZE THIS + + { ACCEPT, "I accept" }, // license + { ALL, "All (everything, including sources)" }, // installation type + { BROWSE, "Browse..." }, // button (open the JFileChooser) + { CANCEL, "Cancel" }, // button + { CHOOSE_LOCATION, "Choose the location where you want Jython to be installed to" }, // selection + { CHOOSE_JRE, "Choose the java version (JRE/JDK) to run Jython with" }, // selection + { CONFIRM_START, "Please press {0} to start the installation" }, // overview + { CONGRATULATIONS, "Congratulations!" }, // congratulations + { CORE, "Core" }, // installation type + { CREATED_DIRECTORY, "Created directory {0}" }, // directory + { CURRENT, "Current" }, // directory + { CUSTOM, "Custom" }, // installation type + { DEMOS_EXAMPLES, "Demos and examples" }, // installation type + { DO_NOT_ACCEPT, "I do not accept" }, // license + { DIRECTORIES_ONLY, "Directories only" }, // file chooser + { DOCUMENTATION, "Documentation" }, // installation type + { EMPTY_TARGET_DIRECTORY, "Target directory must not be empty" }, // error + { ENGLISH, "English" }, // language + { ERROR, "Error" }, // error + { ERROR_ACCESS_JARFILE, "Error accessing jar file" }, // error + { FINISH, "Finish" }, // button + { GENERATING_START_SCRIPTS, "Generating start scripts ..." }, // progress + { GERMAN, "German" }, // language + { INFLATING, "Inflating {0}" }, // progress + { INFORMATION, "Information" }, // information + { INSTALLATION_CANCELLED, "Installation cancelled." }, // final + { INSTALLATION_IN_PROGRESS, "The installation is now in progress" }, // progress + { INSTALLATION_TYPE_DESCRIPTION, "The following installation types are available" }, // installation type + { INSTALLATION_TYPE, "Installation type" }, // installation type + { JAR_NOT_FOUND, "Unable to find jar file {0}." }, // error + { JAVA_INFO, "Java vendor / version" }, // version + { JYTHON_INSTALL, "Jython Installation" }, // title + { LANGUAGE_PROPERTY, "Language" }, // language + { LIBRARY_MODULES, "Library modules" }, // installation type + { LICENSE, "License agreement" }, // license + { MAYBE_NOT_SUPPORTED, "Maybe not supported" }, // version + { MINIMUM, "Minimum (core)" }, // installation type + { NEXT, "Next" }, // button + { NON_EMPTY_TARGET_DIRECTORY, "Target directory is not empty" }, // error + { NO_MANIFEST, "No manifest found in jar file {0}." }, // error + { NOT_OK, "Not ok !" }, // version + { OK, "Ok" }, // version + { OS_INFO, "OS name / version" }, // version + { OTHER, "Other" }, // directory + { OVERVIEW_DESCRIPTION, "The installation will be done using the following options" }, // overview + { OVERVIEW_TITLE, "Overview (summary of options)" }, // overview + { PACKING_STANDALONE_JAR, "Packing standalone " + JarInstaller.JYTHON_JAR + " ..." }, // progress + { PLEASE_ACCEPT_LICENSE, "Please read and accept the license agreement" }, // license + { PLEASE_README, "Please read the following information" }, // readme + { PLEASE_READ_LICENSE, "Please read the license agreement carefully" }, // license + { PLEASE_WAIT, "Please stand by, this may take a few seconds ..." }, // progress + { PRESS_FINISH, "Please press {0} to exit the installation." }, // finish + { PREVIOUS, "Previous" }, // button + { PROGRESS, "Progress" }, // progress + { README, "README" }, // readme + { SELECT, "Select" }, // button (approval in JFileChooser) + { SELECT_INSTALLATION_TYPE, "Please select the installation type" }, // installation type + { SELECT_JAVA_HOME, "Please select the java home directory" }, // directory + { SELECT_LANGUAGE, "Please select your language" }, // language + { SELECT_TARGET_DIRECTORY, "Please select the target directory" }, // directory + { SOURCES, "Sources" }, // installation type + { STANDARD, "Standard (core, library modules, demos, examples, documentation)" }, // installation type + { STANDALONE, "Standalone (a callable .jar file)" }, // installation type + { SUCCESS, "You successfully installed Jython {0} to directory {1}." }, // success + { TARGET_DIRECTORY_PROPERTY, "Target directory" }, // property as title + { TARGET_JAVA_HOME_PROPERTY, "Target java home" }, // property as title + { UNABLE_CREATE_DIRECTORY, "Unable to create directory {0}." }, // error + { UNABLE_CREATE_FILE, "Unable to create file {0}." }, // error + { UNABLE_TO_DELETE, "Unable to delete {0}" }, // error + { UNEXPECTED_URL, "Unexpected URL {0} found for installation jar file." }, // error + { VERSION_INFO, "You are about to install Jython version {0}" }, // version + { WELCOME_TO_JYTHON, "Welcome to Jython !" }, // welcome + { ZIP_ENTRY_SIZE, "Size of zip entry {0} unknown." }, // error + { ZIP_ENTRY_TOO_BIG, "Zip entry {0} too big." }, // error + + // console texts (C_*) should not contain special characters (like e.g. ü) + { C_ACCEPT, "Do you accept the license agreement ?" }, // license + { C_ALL, "All (everything, including sources)" }, // installation type + { C_AT_ANY_TIME_CANCEL, "(at any time, answer {0} to cancel the installation)" }, // console + { C_AVAILABLE_LANGUAGES, "For the installation process, the following languages are available: {0}" }, // console + { C_CHECK_JAVA_VERSION, "Checking java version ..." }, // progress + { C_CLEAR_DIRECTORY, "Contents of directory {0} will be deleted now! Are you sure to proceed ?" }, //console + { C_CONFIRM_TARGET, "Please confirm copying of files to directory {0}" }, // console + { C_CONGRATULATIONS, "Congratulations!" }, // congratulations + { C_CREATE_DIRECTORY, "Unable to find directory {0}, create it ?" }, // console + { C_ENTER_TARGET_DIRECTORY, "Please enter the target directory" }, // console + { C_ENTER_JAVA_HOME, "Please enter the java home directory (empty for using the current java runtime)" }, // console + { C_ENGLISH, "English" }, // language + { C_EXCLUDE, "Do you want to exclude parts from the installation ?" }, // installation type + { C_GENERATING_START_SCRIPTS, "Generating start scripts ..." }, // progress + { C_GERMAN, "German" }, // language + { C_INCLUDE, "Do you want to install additional parts ?" }, // installation type + { C_INEXCLUDE_PARTS, "The following parts are selectable ({0} = no more)" }, // installation type + { C_INSTALL_TYPES, "The following installation types are available:" }, // installation type + { C_INVALID_ANSWER, "Answer {0} is not valid here" }, // error + { C_JAVA_VERSION, "Your java version to start Jython is: {0} / {1}" }, // version + { C_MINIMUM, "Minimum (core)" }, // installation type + { C_NO, "n" }, // answer + { C_NO_BIN_DIRECTORY, "There is no /bin directory below {0}." }, // error + { C_NO_JAVA_EXECUTABLE, "No java executable found in {0}." }, // error + { C_NO_VALID_JAVA, "No valid java found in {0}." }, // error + { C_NON_EMPTY_TARGET_DIRECTORY, "Target directory {0} is not empty" }, // error + { C_NOT_A_DIRECTORY, "{0} is not a directory. " }, // error + { C_NOT_FOUND, "{0} not found. " }, // error + { C_OS_VERSION, "Your operating system version is: {0} / {1}" }, // version + { C_OVERWRITE_DIRECTORY, "Directory {0} is not empty - ok to overwrite contents ?" }, // console + { C_PACKING_STANDALONE_JAR, "Packing standalone " + JarInstaller.JYTHON_JAR + " ..." }, // progress + { C_PROCEED, "Please press Enter to proceed" }, // console + { C_PROCEED_ANYWAY, "Please press Enter to proceed anyway" }, // console + { C_READ_LICENSE, "Do you want to read the license agreement now ?" }, // license + { C_READ_README, "Do you want to show the contents of README ?" }, // readme + { C_SCHEDULED, "{0} scheduled for installation" }, // installation type + { C_SELECT_INSTALL_TYPE, "Please select the installation type" }, // installation type + { C_SELECT_LANGUAGE, "Please select your language" }, // language + { C_SILENT_INSTALLATION, "Performing silent installation" }, // installation mode + { C_STANDALONE, "Standalone (a single, executable .jar)" }, //installation mode + { C_STANDARD, "Standard (core, library modules, demos and examples, documentation)" }, // installation type + { C_SUCCESS, "You successfully installed Jython {0} to directory {1}." }, // success + { C_SUMMARY, "Summary:" }, // summary + { C_TO_CURRENT_JAVA, "Warning: switching back to current JDK due to error: {0}." }, // warning + { C_UNABLE_CREATE_DIRECTORY, "Unable to create directory {0}." }, // error + { C_UNABLE_CREATE_TMPFILE, "Unable to create temp file {0}." }, // error + { C_UNSCHEDULED, "{0} excluded from installation" }, // installation type + { C_UNABLE_TO_DELETE, "Unable to delete {0}" }, // error + { C_UNSUPPORTED_JAVA, "This java version is not supported." }, // version + { C_UNSUPPORTED_OS, "This operating system might not be fully supported." }, // version + { C_USING_TYPE, "Using installation type {0}" }, // installation type + { C_VERSION_INFO, "You are about to install Jython version {0}" }, // version + { C_WELCOME_TO_JYTHON, "Welcome to Jython !" }, // welcome + { C_YES, "y" }, // answer + + // END OF MATERIAL TO LOCALIZE + }; + + public Object[][] getContents() { + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants_de.java b/installer/src/java/org/python/util/install/TextConstants_de.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants_de.java @@ -0,0 +1,148 @@ +package org.python.util.install; + +import java.util.ListResourceBundle; + +public class TextConstants_de extends ListResourceBundle implements TextKeys, UnicodeSequences { + + static final Object[][] contents = { + // Die folgenden Texte duerfen Umlaute und Sonderzeichen enthalten, aber nur als Unicode Escape Sequenzen aus UnicodeSequences + // The following texts may contain special characters, but only as unicode escape sequences from UnicodeSequences + { ACCEPT, "Ja, ich akzeptiere" }, // license + { ALL, "Alles (volle Installation, inklusive Quellcode)" }, // installation type + { BROWSE, "Suchen..." }, // button (open the JFileChooser) + { CANCEL, "Abbrechen" }, // button text + { CHOOSE_LOCATION, "W"+a2+"hlen Sie das Verzeichnis, in das Jython installiert werden soll" }, // selection + { CHOOSE_JRE, "Bestimmen Sie die Java Version (JRE/JDK), mit welcher Jython gestartet werden soll" }, // selection + { CONFIRM_START, "Bitte dr"+u2+"cken Sie {0}, um die Installation zu starten" }, // overview + { CONGRATULATIONS, "Gratulation!" }, // congratulations + { CORE, "Kern" }, // installation type + { CREATED_DIRECTORY, "Verzeichnis {0} wurde erstellt" }, // directory + { CURRENT, "Das aktuelle" }, // directory + { CUSTOM, "Benutzerdefiniert" }, // installation type + { DEMOS_EXAMPLES, "Demos und Beispiele" }, // installation type + { DIRECTORIES_ONLY, "Nur Verzeichnisse" }, // file chooser + { DOCUMENTATION, "Dokumentation" }, // installation type + { DO_NOT_ACCEPT, "Nein, ich akzeptiere nicht" }, // license + { EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis darf nicht leer sein" }, // error + { ENGLISH, "Englisch" }, // language + { ERROR, "Fehler" }, // error + { ERROR_ACCESS_JARFILE, "Problem beim Zugriff auf das jar File" }, // error + { FINISH, "Beenden" }, // button + { GENERATING_START_SCRIPTS, "Start Scripts werden generiert ..." }, // progress + { GERMAN, "Deutsch" }, // language + { INFLATING, "Entpacke {0}" }, // progress + { INFORMATION, "Information" }, // information + { INSTALLATION_CANCELLED, "Sie haben die Installation abgebrochen." }, // final + { INSTALLATION_IN_PROGRESS, "Die Installation l"+a2+"uft" }, // progress + { INSTALLATION_TYPE_DESCRIPTION, "Die folgenden Installationstypen sind verf"+u2+"gbar" }, // installation type + { INSTALLATION_TYPE, "Installationstyp" }, // installation type + { JAVA_INFO, "Java Hersteller / Version" }, // version + { JAR_NOT_FOUND, "Jar File {0} nicht gefunden." }, // error + { JYTHON_INSTALL, "Jython Installation" }, // title + { LANGUAGE_PROPERTY, "Sprache" }, // language + { LIBRARY_MODULES, "Bibliotheksmodule" }, // installation type + { LICENSE, "Lizenzvereinbarung" }, // license + { MAYBE_NOT_SUPPORTED, "Eventuell nicht unterst"+u2+"tzt" }, // version + { MINIMUM, "Minimum (Kern)" }, // installation type + { NEXT, "Weiter" }, // button + { NON_EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis enth"+a2+"lt bereits Daten." }, // error + { NO_MANIFEST, "Jar File {0} enth"+a2+"lt kein Manifest." }, // error + { NOT_OK, "Nicht ok !" }, // version + { OK, "Ok" }, // version + { OS_INFO, "Betriebssystem / Version" }, // version + { OTHER, "Ein abweichendes" }, // directory + { OVERVIEW_DESCRIPTION, "Sie haben folgende Einstellungen f"+u2+"r die Installation ausgew"+a2+"hlt" }, // overview + { OVERVIEW_TITLE, U2+"bersicht "+u2+"ber die gew"+a2+"hlten Einstellungen" }, // overview + { PACKING_STANDALONE_JAR, "Das standalone " + JarInstaller.JYTHON_JAR + " File wird erstellt ..." }, // progress + { PLEASE_ACCEPT_LICENSE, "Bitte lesen und akzeptieren Sie die Lizenzvereinbarung" }, // license + { PLEASE_README, "Bitte lesen Sie die folgenden Informationen" }, // readme + { PLEASE_READ_LICENSE, "Bitte lesen Sie die Lizenzvereinbarung sorf"+a2+"ltig durch" }, // license + { PLEASE_WAIT, "Bitte um etwas Geduld, die Installation kann einige Sekunden dauern ..." }, // progress + { PRESS_FINISH, "Bitte dr"+u2+"cken Sie {0}, um die Installation abzuschliessen." }, // finish + { PREVIOUS, "Zur"+u2+"ck" }, // button + { PROGRESS, "Fortschritt" }, // progress + { README, "README" }, // readme + { SELECT, "Ausw"+a2+"hlen" }, // button (approval in JFileChooser) + { SELECT_INSTALLATION_TYPE, "Bitte w"+a2+"hlen Sie den Installationstyp" }, // installation type + { SELECT_JAVA_HOME, "Bitte w"+a2+"hlen Sie das Java Home Verzeichnis" }, // directory + { SELECT_LANGUAGE, "Bitte w"+a2+"hlen Sie Ihre Sprache" }, // language + { SELECT_TARGET_DIRECTORY, "Bitte w"+a2+"hlen Sie das Zielverzeichnis" }, // directory + { SOURCES, "Quellcode" }, // installation type + { STANDARD, "Standard (Kern, Bibliotheksmodule, Demos, Beispiele, Dokumentation)" }, // installation type + { STANDALONE, "Standalone (ein ausf"+u2+"hrbares .jar File)" }, // installation type + { SUCCESS, "Sie haben Jython {0} erfolgreich im Verzeichnis {1} installiert." }, // final + { TARGET_DIRECTORY_PROPERTY, "Zielverzeichnis" }, // property als Titel + { TARGET_JAVA_HOME_PROPERTY, "Java Home Verzeichnis" }, // property als Titel + { UNABLE_CREATE_DIRECTORY, "Fehler beim Erstellen von Verzeichnis {0}." }, // error + { UNABLE_CREATE_FILE, "Fehler beim Erstellen von File {0}." }, // error + { UNABLE_TO_DELETE, "Fehler beim L"+o2+"schen von {0}" }, // console + { UNEXPECTED_URL, "Das Jar File f"+u2+"r die Installation weist eine unerwartete URL {0} auf." }, // error + { VERSION_INFO, "Sie sind im Begriff, Jython Version {0} zu installieren." }, // version + { WELCOME_TO_JYTHON, "Willkommen bei Jython !" }, // welcome + { ZIP_ENTRY_SIZE, "Der Zip Eintrag {0} hat eine unbekannte Gr"+o2+"sse." }, // error + { ZIP_ENTRY_TOO_BIG, "Der Zip Eintrag {0} ist zu gross." }, // error + + // Konsole Texte (beginnend mit C_) duerfen keine Umlaute und andere Sonderzeichen enthalten: + // console texts (beginning with C_) must not contain special characters (use ASCII only): + { C_ACCEPT, "Akzeptieren Sie die Lizenzvereinbarung ?" }, // license + { C_ALL, "Alles (volle Installation, inklusive Quellcode)" }, // installation type + { C_AT_ANY_TIME_CANCEL, "(Sie koennen die Installation jederzeit durch Eingabe von {0} abbrechen)" }, // console + { C_AVAILABLE_LANGUAGES, "Die folgenden Sprachen sind fuer den Installationsvorgang verfuegbar: {0}" }, // languages + { C_CHECK_JAVA_VERSION, "Ueberpruefung der Java Version ..." }, // progress + { C_CLEAR_DIRECTORY, "Der Inhalt von Verzeichnis {0} wird anschliessend geloescht! Moechten Sie wirklich weiterfahren ?" }, //console + { C_CONFIRM_TARGET, "Bitte bestaetigen Sie den Start des Kopiervorgangs ins Verzeichnis {0}" }, // console + { C_CONGRATULATIONS, "Gratulation!" }, // congratulations + { C_CREATE_DIRECTORY, "Das Verzeichnis {0} gibt es nicht - soll es erstellt werden ?" }, // console + { C_ENTER_TARGET_DIRECTORY, "Bitte geben Sie das Zielverzeichnis ein" }, // console + { C_ENTER_JAVA_HOME, "Bitte geben Sie das gewuenschte Java Home Verzeichnis ein (Enter fuer das aktuelle)" }, // console + { C_ENGLISH, "Englisch" }, // language + { C_EXCLUDE, "Moechten Sie Teile von der Installation ausschliessen ?" }, // installation type + { C_GENERATING_START_SCRIPTS, "Start Scripts werden generiert ..." }, // progress + { C_GERMAN, "Deutsch" }, // language + { C_INCLUDE, "Moechten Sie weitere Teile installieren ?" }, // installation type + { C_INEXCLUDE_PARTS, "Folgende Teile stehen zur Auswahl ({0} = keine weiteren)" }, // installation type + { C_INSTALL_TYPES, "Die folgenden Installationstypen sind verfuegbar:" }, // installation type + { C_INVALID_ANSWER, "Die Antwort {0} ist hier nicht gueltig" }, // error + { C_JAVA_VERSION, "Ihre Java Version fuer den Start von Jython ist: {0} / {1}" }, // version + { C_MINIMUM, "Minimum (Kern)" }, // installation type + { C_NO, "n" }, // answer + { C_NO_BIN_DIRECTORY, "Es gibt kein /bin Verzeichnis unterhalb {0}." }, //error + { C_NO_JAVA_EXECUTABLE, "Es gibt kein ausfuehrbares java in {0}." }, // error + { C_NO_VALID_JAVA, "Keine gueltige Java Version gefunden in {0}." }, // error + { C_NON_EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis {0} enthaelt bereits Daten" }, // error + { C_NOT_A_DIRECTORY, "{0} ist kein Verzeichnis. " }, // error + { C_NOT_FOUND, "{0} nicht gefunden." }, // error + { C_OS_VERSION, "Ihre Betriebssystem Version ist: {0} / {1}" }, // version + { C_OVERWRITE_DIRECTORY, "Das Verzeichnis {0} enthaelt bereits Daten, und die Installation wuerde diese ueberschreiben - ok ?" }, // console + { C_PACKING_STANDALONE_JAR, "Das standalone " + JarInstaller.JYTHON_JAR + " File wird erstellt ..." }, // progress + { C_PROCEED, "Bitte druecken Sie Enter um weiterzufahren" }, // console + { C_PROCEED_ANYWAY, "Bitte druecken Sie Enter um trotzdem weiterzufahren" }, // console + { C_READ_LICENSE, "Moechten Sie die Lizenzvereinbarung jetzt lesen ?" }, // license + { C_READ_README, "Moechten Sie den Inhalt von README jetzt lesen ?" }, // readme + { C_SCHEDULED, "{0} zur Installation vorgemerkt" }, // installation type + { C_SELECT_INSTALL_TYPE, "Bitte waehlen Sie den Installationstyp" }, // installation type + { C_SELECT_LANGUAGE, "Bitte waehlen Sie Ihre Sprache" }, // language + { C_SILENT_INSTALLATION, "Die Installation wird ohne Benutzerinteraktion ausgefuehrt" }, // installation mode + { C_STANDALONE, "Standalone (ein ausfuehrbares .jar File)" }, //installation mode + { C_STANDARD, "Standard (Kern, Bibliotheksmodule, Demos und Beispiele, Dokumentation)" }, // installation type + { C_SUCCESS, "Sie haben Jython {0} erfolgreich im Verzeichnis {1} installiert." }, // final + { C_SUMMARY, "Zusammenfassung:" }, // summary + { C_TO_CURRENT_JAVA, "Warnung: Wechsel zum aktuellen JDK wegen Fehler: {0}." }, // warning + { C_UNABLE_CREATE_DIRECTORY, "Fehler beim Erstellen von Verzeichnis {0}." }, // error + { C_UNABLE_CREATE_TMPFILE, "Fehler beim Erstellen der temporaeren Datei {0}." }, // error + { C_UNABLE_TO_DELETE, "Fehler beim Loeschen von {0}" }, // console + { C_UNSCHEDULED, "{0} von der Installation ausgeschlossen" }, // installation type + { C_UNSUPPORTED_JAVA, "Diese Java Version ist nicht unterstuetzt." }, // version + { C_UNSUPPORTED_OS, "Dieses Betriebssystem ist eventuell nicht vollstaendig unterstuetzt." }, // version + { C_USING_TYPE, "Installationstyp ist {0}" }, // installation type + { C_VERSION_INFO, "Sie sind im Begriff, Jython Version {0} zu installieren." }, // version + { C_WELCOME_TO_JYTHON, "Willkommen bei Jython !" }, // welcome + { C_YES, "j" }, // answer + + }; + + public Object[][] getContents() { + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants_en.java b/installer/src/java/org/python/util/install/TextConstants_en.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants_en.java @@ -0,0 +1,5 @@ +package org.python.util.install; + +public class TextConstants_en extends TextConstants { + // need this as placeholder +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextKeys.java b/installer/src/java/org/python/util/install/TextKeys.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextKeys.java @@ -0,0 +1,135 @@ +package org.python.util.install; + +public interface TextKeys { + public static final String ACCEPT = "ACCEPT"; + public static final String ACCEPT_PROPERTY = "ACCEPT_PROPERTY"; + public static final String ALL = "ALL"; + public static final String BROWSE = "BROWSE"; + public static final String CANCEL = "CANCEL"; + public static final String CHOOSE_LOCATION = "CHOOSE_LOCATION"; + public static final String CHOOSE_JRE = "CHOOSE_JRE"; + public static final String CONFIRM_START = "CONFIRM_START"; + public static final String CONGRATULATIONS = "CONGRATULATIONS"; + public static final String CORE = "CORE"; + public static final String CREATED_DIRECTORY = "CREATED_DIRECTORY"; + public static final String CURRENT = "CURRENT"; + public static final String CUSTOM = "CUSTOM"; + public static final String DEMOS_EXAMPLES = "DEMOS_EXAMPLES"; + public static final String DIRECTORIES_ONLY = "DIRECTORIES_ONLY"; + public static final String DOCUMENTATION = "DOCUMENTATION"; + public static final String DO_NOT_ACCEPT = "DO_NOT_ACCEPT"; + public static final String EMPTY_TARGET_DIRECTORY = "EMPTY_TARGET_DIRECTORY"; + public static final String ENGLISH = "ENGLISH"; + public static final String ERROR = "ERROR"; + public static final String ERROR_ACCESS_JARFILE = "ERROR_ACCESS_JARFILE"; + public static final String FINISH = "FINISH"; + public static final String GENERATING_START_SCRIPTS = "GENERATE_START_SCRIPTS"; + public static final String GERMAN = "GERMAN"; + public static final String INFLATING = "INFLATING"; + public static final String INFORMATION = "INFORMATION"; + public static final String INSTALLATION_CANCELLED = "INSTALLATION_CANCELLED"; + public static final String INSTALLATION_IN_PROGRESS = "INSTALLATION_IN_PROGRESS"; + public static final String INSTALLATION_TYPE_DESCRIPTION = "INSTALLATION_TYPE_DESCRIPTION"; + public static final String INSTALLATION_TYPE = "INSTALLATION_TYPE"; + public static final String JAR_NOT_FOUND = "JAR_NOT_FOUND"; + public static final String JAVA_INFO = "JAVA_INFO"; + public static final String JYTHON_INSTALL = "JYTHON_INSTALL"; + public static final String LANGUAGE_PROPERTY = "LANGUAGE_PROPERTY"; + public static final String LIBRARY_MODULES = "LIBRARY_MODULES"; + public static final String LICENSE = "LICENSE"; + public static final String MAYBE_NOT_SUPPORTED = "MAYBE_NOT_SUPPORTED"; + public static final String MINIMUM = "MINIMUM"; + public static final String NEXT = "NEXT"; + public static final String NON_EMPTY_TARGET_DIRECTORY = "NON_EMPTY_TARGET_DIRECTORY"; + public static final String NO_MANIFEST = "NO_MANIFEST"; + public static final String NOT_OK = "NOT_OK"; + public static final String OK = "OK"; + public static final String OS_INFO = "OS_INFO"; + public static final String OTHER = "OTHER"; + public static final String OVERVIEW_DESCRIPTION = "OVERVIEW_DESCRIPTION"; + public static final String OVERVIEW_TITLE = "OVERVIEW_TITLE"; + public static final String PACKING_STANDALONE_JAR = "PACKING_STANDALONE_JAR"; + public static final String PLEASE_ACCEPT_LICENSE = "PLEASE_ACCEPT_LICENSE"; + public static final String PLEASE_README = "PLEASE_README"; + public static final String PLEASE_READ_LICENSE = "PLEASE_READ_LICENSE"; + public static final String PLEASE_WAIT = "PLEASE_WAIT"; + public static final String PRESS_FINISH = "PRESS_FINISH"; + public static final String PREVIOUS = "PREVIOUS"; + public static final String PROGRESS = "PROGRESS"; + public static final String README = "README"; + public static final String SELECT = "SELECT"; + public static final String SELECT_INSTALLATION_TYPE = "SELECT_INSTALLATION_TYPE"; + public static final String SELECT_JAVA_HOME = "SELECT_JAVA_HOME"; + public static final String SELECT_LANGUAGE = "SELECT_LANGUAGE"; + public static final String SELECT_TARGET_DIRECTORY = "SELECT_TARGET_DIRECTORY"; + public static final String SOURCES = "SOURCES"; + public static final String STANDARD = "STANDARD"; + public static final String STANDALONE = "STANDALONE"; + public static final String SUCCESS = "SUCCESS"; + public static final String TARGET_DIRECTORY_PROPERTY = "TARGET_DIRECTORY_PROPERTY"; + public static final String TARGET_JAVA_HOME_PROPERTY = "TARGET_JAVA_HOME_PROPERTY"; + public static final String UNABLE_CREATE_DIRECTORY = "UNABLE_CREATE_DIRECTORY"; + public static final String UNABLE_CREATE_FILE = "UNABLE_CREATE_FILE"; + public static final String UNABLE_TO_DELETE = "UNABLE_TO_DELETE"; + public static final String UNEXPECTED_URL = "UNEXPECTED_URL"; + public static final String VERSION_INFO = "VERSION_INFO"; + public static final String WELCOME_TO_JYTHON = "WELCOME_TO_JYTHON"; + public static final String ZIP_ENTRY_SIZE = "ZIP_ENTRY_SIZE"; + public static final String ZIP_ENTRY_TOO_BIG = "ZIP_ENTRY_TOO_BIG"; + + // console texts + public static final String C_ACCEPT = "C_ACCEPT"; + public static final String C_ALL = "C_ALL"; + public static final String C_AT_ANY_TIME_CANCEL = "C_AT_ANY_TIME_CANCEL"; + public static final String C_AVAILABLE_LANGUAGES = "C_AVAILABLE_LANGUAGES"; + public static final String C_CHECK_JAVA_VERSION = "C_CHECK_JAVA_VERSION"; + public static final String C_CLEAR_DIRECTORY = "C_CLEAR_DIRECTORY"; + public static final String C_CONFIRM_TARGET = "C_CONFIRM_TARGET"; + public static final String C_CONGRATULATIONS = "C_CONGRATULATIONS"; + public static final String C_CREATE_DIRECTORY = "C_CREATE_DIRECTORY"; + public static final String C_ENGLISH = "C_ENGLISH"; + public static final String C_ENTER_TARGET_DIRECTORY = "C_ENTER_TARGET_DIRECTORY"; + public static final String C_ENTER_JAVA_HOME = "C_ENTER_JAVA_HOME"; + public static final String C_EXCLUDE = "C_EXCLUDE"; + public static final String C_GENERATING_START_SCRIPTS = "C_GENERATE_START_SCRIPTS"; + public static final String C_GERMAN = "C_GERMAN"; + public static final String C_INCLUDE = "C_INCLUDE"; + public static final String C_INEXCLUDE_PARTS = "C_INEXCLUDE_PARTS"; + public static final String C_INSTALL_TYPES = "C_INSTALL_TYPES"; + public static final String C_INVALID_ANSWER = "C_INVALID_ANSWER"; + public static final String C_JAVA_VERSION = "C_JAVA_VERSION"; + public static final String C_MINIMUM = "C_MINIMUM"; + public static final String C_NO = "C_NO"; + public static final String C_NO_BIN_DIRECTORY = "C_NO_BIN_DIRECTORY"; + public static final String C_NO_JAVA_EXECUTABLE = "C_NO_JAVA_EXECUTABLE"; + public static final String C_NO_VALID_JAVA = "C_NO_VALID_JAVA"; + public static final String C_NON_EMPTY_TARGET_DIRECTORY = "C_NON_EMPTY_TARGET_DIRECTORY"; + public static final String C_NOT_A_DIRECTORY = "C_NOT_A_DIRECTORY"; + public static final String C_NOT_FOUND = "C_NOT_FOUND"; + public static final String C_OS_VERSION = "C_OS_VERSION"; + public static final String C_OVERWRITE_DIRECTORY = "C_OVERWRITE_DIRECTORY"; + public static final String C_PACKING_STANDALONE_JAR = "C_PACKING_STANDALONE_JAR"; + public static final String C_PROCEED = "C_PROCEED"; + public static final String C_PROCEED_ANYWAY = "C_PROCEED_ANYWAY"; + public static final String C_READ_LICENSE = "C_READ_LICENSE"; + public static final String C_READ_README = "C_READ_README"; + public static final String C_SCHEDULED = "C_SCHEDULED"; + public static final String C_SELECT_INSTALL_TYPE = "C_SELECT_INSTALL_TYPE"; + public static final String C_SELECT_LANGUAGE = "C_SELECT_LANGUAGE"; + public static final String C_SILENT_INSTALLATION = "C_SILENT_INSTALLATION"; + public static final String C_STANDALONE = "C_STANDALONE"; + public static final String C_STANDARD = "C_STANDARD"; + public static final String C_SUCCESS = "C_SUCCESS"; + public static final String C_SUMMARY = "C_SUMMARY"; + public static final String C_UNABLE_CREATE_TMPFILE = "C_UNABLE_CREATE_TMPFILE"; + public static final String C_TO_CURRENT_JAVA = "C_TO_CURRENT_JAVA"; + public static final String C_UNABLE_CREATE_DIRECTORY = "C_UNABLE_CREATE_DIRECTORY"; + public static final String C_UNABLE_TO_DELETE = "C_UNABLE_TO_DELETE"; + public static final String C_UNSCHEDULED = "C_UNSCHEDULED"; + public static final String C_UNSUPPORTED_JAVA = "C_UNSUPPORTED_JAVA"; + public static final String C_UNSUPPORTED_OS = "C_UNSUPPORTED_OS"; + public static final String C_USING_TYPE = "C_USING_TYPE"; + public static final String C_VERSION_INFO = "C_VERSION_INFO"; + public static final String C_WELCOME_TO_JYTHON = "C_WELCOME_TO_JYTHON"; + public static final String C_YES = "C_YES"; +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TypePage.java b/installer/src/java/org/python/util/install/TypePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TypePage.java @@ -0,0 +1,268 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +public class TypePage extends AbstractWizardPage { + + private static final String _CUSTOM_ACTION_COMMAND = "custom"; + + private JLabel _label; + private JRadioButton _allButton; + private JRadioButton _standardButton; + private JRadioButton _minimumButton; + private JRadioButton _standaloneButton; + private JRadioButton _customButton; + + private JCheckBox _core; + private JCheckBox _mod; + private JCheckBox _demo; + private JCheckBox _doc; + private JCheckBox _src; + + private boolean _firstTime = true; + + public TypePage() { + super(); + initComponents(); + } + + private void initComponents() { + TypeChangeListener typeChangeListener = new TypeChangeListener(); + + _label = new JLabel(); + + // radio buttons + _allButton = new JRadioButton(); + _allButton.setActionCommand(Installation.ALL); + _allButton.addActionListener(typeChangeListener); + _standardButton = new JRadioButton(); + _standardButton.setActionCommand(Installation.STANDARD); + _standardButton.addActionListener(typeChangeListener); + _minimumButton = new JRadioButton(); + _minimumButton.setActionCommand(Installation.MINIMUM); + _minimumButton.addActionListener(typeChangeListener); + _standaloneButton = new JRadioButton(); + _standaloneButton.setActionCommand(Installation.STANDALONE); + _standaloneButton.addActionListener(typeChangeListener); + _customButton = new JRadioButton(); + _customButton.setActionCommand(_CUSTOM_ACTION_COMMAND); + _customButton.addActionListener(typeChangeListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_allButton); + radioButtonGroup.add(_standardButton); + radioButtonGroup.add(_minimumButton); + radioButtonGroup.add(_standaloneButton); + radioButtonGroup.add(_customButton); + JPanel radioPanel = new JPanel(new GridLayout(0, 1)); + radioPanel.add(_allButton); + radioPanel.add(_standardButton); + radioPanel.add(_minimumButton); + radioPanel.add(_standaloneButton); + radioPanel.add(_customButton); + + // check boxes + _core = new JCheckBox(); + _core.setEnabled(false); + _mod = new JCheckBox(); + _mod.setEnabled(true); + _mod.setActionCommand(InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES); + _mod.addActionListener(typeChangeListener); + _demo = new JCheckBox(); + _demo.setEnabled(true); + _demo.setActionCommand(InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES); + _demo.addActionListener(typeChangeListener); + _doc = new JCheckBox(); + _doc.setEnabled(true); + _doc.setActionCommand(InstallerCommandLine.INEXCLUDE_DOCUMENTATION); + _doc.addActionListener(typeChangeListener); + _src = new JCheckBox(); + _src.setEnabled(true); + _src.setActionCommand(InstallerCommandLine.INEXCLUDE_SOURCES); + _src.addActionListener(typeChangeListener); + + JPanel checkboxPanel = new JPanel(); + GridLayout gridLayout = new GridLayout(5, 1); + checkboxPanel.setLayout(gridLayout); + checkboxPanel.add(_core); + checkboxPanel.add(_mod); + checkboxPanel.add(_demo); + checkboxPanel.add(_doc); + checkboxPanel.add(_src); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 2; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridwidth = 1; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(radioPanel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(checkboxPanel, gridBagConstraints); + + add(panel); + } + + protected String getTitle() { + return getText(INSTALLATION_TYPE); + } + + protected String getDescription() { + return getText(INSTALLATION_TYPE_DESCRIPTION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + InstallationType installationType = getInstallationType(); + if (installationType.isAll()) { + return _allButton; + } else if (installationType.isMinimum()) { + return _minimumButton; + } else if (installationType.isStandalone()) { + return _standaloneButton; + } else if (installationType.isStandard()) { + return _standardButton; + } else { + return _customButton; + } + } + + protected void activate() { + _label.setText(getText(SELECT_INSTALLATION_TYPE) + ": "); + _allButton.setText(getText(ALL)); + _standardButton.setText(getText(STANDARD)); + _minimumButton.setText(getText(MINIMUM)); + _standaloneButton.setText(getText(STANDALONE)); + _customButton.setText(getText(CUSTOM)); + InstallationType installationType = getInstallationType(); + if (installationType.isAll()) { + _allButton.setSelected(true); + } else if (installationType.isMinimum()) { + _minimumButton.setSelected(true); + } else if (installationType.isStandalone()) { + _standaloneButton.setSelected(true); + } else if (installationType.isStandard()) { + _standardButton.setSelected(true); + } else { + _customButton.setSelected(true); + } + _core.setText(getText(CORE)); + _mod.setText(getText(LIBRARY_MODULES)); + _demo.setText(getText(DEMOS_EXAMPLES)); + _doc.setText(getText(DOCUMENTATION)); + _src.setText(getText(SOURCES)); + setCheckboxes(installationType); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private InstallationType getInstallationType() { + InstallationType installationType; + if (_firstTime) { + _firstTime = false; + installationType = new InstallationType(); + installationType.setStandard(); + FrameInstaller.setInstallationType(installationType); + } + installationType = FrameInstaller.getInstallationType(); + return installationType; + } + + private final class TypeChangeListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + InstallationType installationType = FrameInstaller.getInstallationType(); + String actionCommand = e.getActionCommand(); + if (Installation.ALL.equals(actionCommand)) { + installationType.setAll(); + setCheckboxes(installationType); + } else if (Installation.STANDARD.equals(actionCommand)) { + installationType.setStandard(); + setCheckboxes(installationType); + } else if (Installation.MINIMUM.equals(actionCommand)) { + installationType.setMinimum(); + setCheckboxes(installationType); + } else if (Installation.STANDALONE.equals(actionCommand)) { + installationType.setStandalone(); + setCheckboxes(installationType); + } else if (_CUSTOM_ACTION_COMMAND.equals(actionCommand)) { + _mod.setEnabled(true); + _demo.setEnabled(true); + _doc.setEnabled(true); + _src.setEnabled(true); + } else { + boolean selected = ((JCheckBox) e.getSource()).isSelected(); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(actionCommand)) { + if (selected) { + installationType.addLibraryModules(); + } else { + installationType.removeLibraryModules(); + } + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(actionCommand)) { + if (selected) { + installationType.addDemosAndExamples(); + } else { + installationType.removeDemosAndExamples(); + } + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(actionCommand)) { + if (selected) { + installationType.addDocumentation(); + } else { + installationType.removeDocumentation(); + } + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(actionCommand)) { + if (selected) { + installationType.addSources(); + } else { + installationType.removeSources(); + } + } + } + FrameInstaller.setInstallationType(installationType); + } + } + + void setCheckboxes(InstallationType installationType) { + _core.setSelected(true); + _mod.setSelected(installationType.installLibraryModules()); + _demo.setSelected(installationType.installDemosAndExamples()); + _doc.setSelected(installationType.installDocumentation()); + _src.setSelected(installationType.installSources()); + _standaloneButton.setSelected(installationType.isStandalone()); + _mod.setEnabled(!installationType.isPredefined()); + _demo.setEnabled(!installationType.isPredefined()); + _doc.setEnabled(!installationType.isPredefined()); + _src.setEnabled(!installationType.isPredefined()); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/UnicodeSequences.java b/installer/src/java/org/python/util/install/UnicodeSequences.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/UnicodeSequences.java @@ -0,0 +1,19 @@ +package org.python.util.install; + +/** + * Some unicode special characters + * + * @see http://www.unicode.org/charts/PDF/U0080.pdf + */ +public interface UnicodeSequences { + + public static final String A2 = "\u00C4"; // German A umlaut: A with two dots above + public static final String a2 = "\u00E4"; // German a umlaut: a with two dots above + + public static final String O2 = "\u00D6"; // German O umlaut: O with two dots above + public static final String o2 = "\u00F6"; // German o umlaut: o with two dots above + + public static final String U2 = "\u00DC"; // German U umlaut: U with two dots above + public static final String u2 = "\u00FC"; // German u umlaut: u with two dots above + +} diff --git a/installer/src/java/org/python/util/install/ValidationEvent.java b/installer/src/java/org/python/util/install/ValidationEvent.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationEvent.java @@ -0,0 +1,14 @@ +package org.python.util.install; + +import java.util.EventObject; + +public class ValidationEvent extends EventObject { + + public ValidationEvent(AbstractWizardPage source) { + super(source); + } + + public AbstractWizardPage getWizardPage() { + return (AbstractWizardPage) source; + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationException.java b/installer/src/java/org/python/util/install/ValidationException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationException.java @@ -0,0 +1,19 @@ +package org.python.util.install; + +public class ValidationException extends Exception { + public ValidationException() { + super(); + } + + public ValidationException(String message) { + super(message); + } + + public ValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationInformationException.java b/installer/src/java/org/python/util/install/ValidationInformationException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationInformationException.java @@ -0,0 +1,21 @@ +package org.python.util.install; + +public class ValidationInformationException extends Exception { + + public ValidationInformationException() { + super(); + } + + public ValidationInformationException(String message) { + super(message); + } + + public ValidationInformationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationInformationException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationListener.java b/installer/src/java/org/python/util/install/ValidationListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationListener.java @@ -0,0 +1,11 @@ +package org.python.util.install; + +public interface ValidationListener { + public void validationFailed(ValidationEvent event, ValidationException exception); + + public void validationInformationRequired(ValidationEvent event, ValidationInformationException exception); + + public void validationStarted(ValidationEvent event); + + public void validationSucceeded(ValidationEvent event); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/Wizard.java b/installer/src/java/org/python/util/install/Wizard.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/Wizard.java @@ -0,0 +1,90 @@ +package org.python.util.install; + +import java.awt.Dimension; +import java.awt.Rectangle; + +import javax.swing.JOptionPane; + +import org.python.util.install.driver.Autotest; + +public class Wizard extends AbstractWizard implements TextKeys { + + public Wizard(JarInfo jarInfo, Autotest autotest) { + super(); + + setTitle(getText(JYTHON_INSTALL)); + + LanguagePage languagePage = new LanguagePage(jarInfo); + LicensePage licensePage = new LicensePage(jarInfo); + licensePage.setValidator(new LicensePageValidator(licensePage)); + TypePage typePage = new TypePage(); + DirectorySelectionPage directoryPage = new DirectorySelectionPage(jarInfo); + directoryPage.setValidator(new DirectorySelectionPageValidator(directoryPage)); + JavaSelectionPage javaPage = new JavaSelectionPage(); + javaPage.setValidator(new JavaSelectionPageValidator(javaPage)); + OverviewPage overviewPage = new OverviewPage(); + ProgressPage progressPage = new ProgressPage(jarInfo, autotest); + ReadmePage readmePage = new ReadmePage(jarInfo); + SuccessPage successPage = new SuccessPage(jarInfo); + + this.addPage(languagePage); + this.addPage(licensePage); + this.addPage(typePage); + this.addPage(directoryPage); + this.addPage(javaPage); + this.addPage(overviewPage); + this.addPage(progressPage); + this.addPage(readmePage); + this.addPage(successPage); + + setSize(720, 330); + centerOnScreen(); + validate(); + } + + protected boolean finish() { + return true; + } + + protected String getCancelString() { + return getText(CANCEL); + } + + protected String getFinishString() { + return getText(FINISH); + } + + protected String getNextString() { + return getText(NEXT); + } + + protected String getPreviousString() { + return getText(PREVIOUS); + } + + public void validationStarted(ValidationEvent event) { + } + + public void validationFailed(ValidationEvent event, ValidationException exception) { + JOptionPane.showMessageDialog(this, exception.getMessage(), getText(TextKeys.ERROR), JOptionPane.ERROR_MESSAGE); + } + + public void validationInformationRequired(ValidationEvent event, ValidationInformationException exception) { + JOptionPane.showMessageDialog(this, exception.getMessage(), getText(INFORMATION), + JOptionPane.INFORMATION_MESSAGE); + } + + public void validationSucceeded(ValidationEvent event) { + } + + private final String getText(String textKey) { + return Installation.getText(textKey); + } + + private void centerOnScreen() { + Dimension dim = getToolkit().getScreenSize(); + Rectangle rectBounds = getBounds(); + setLocation((dim.width - rectBounds.width) / 2, (dim.height - rectBounds.height) / 2); + } + +} diff --git a/installer/src/java/org/python/util/install/WizardEvent.java b/installer/src/java/org/python/util/install/WizardEvent.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardEvent.java @@ -0,0 +1,13 @@ +package org.python.util.install; + +import java.util.EventObject; + +public class WizardEvent extends EventObject { + public WizardEvent(AbstractWizard source) { + super(source); + } + + public AbstractWizard getWizard() { + return (AbstractWizard) source; + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/WizardHeader.java b/installer/src/java/org/python/util/install/WizardHeader.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardHeader.java @@ -0,0 +1,89 @@ +package org.python.util.install; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JSeparator; + +public class WizardHeader extends AbstractWizardHeader { + private static final Dimension _iconSize = new Dimension(100, 60); + + private JLabel _descriptionLabel; + private JSeparator _headerSeparator; + private JLabel _iconLabel; + private JLabel _titleLabel; + + WizardHeader() { + super(); + initComponents(); + } + + private void initComponents() { + GridBagConstraints gridBagConstraints; + + _titleLabel = new JLabel(); + _descriptionLabel = new JLabel(); + _iconLabel = new JLabel(); + _headerSeparator = new JSeparator(); + + setLayout(new GridBagLayout()); + + setBackground(new Color(255, 255, 255)); + _titleLabel.setFont(_titleLabel.getFont().deriveFont(Font.BOLD, 14f)); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new Insets(2, 2, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + add(_titleLabel, gridBagConstraints); + + _descriptionLabel.setFont(_descriptionLabel.getFont().deriveFont(Font.PLAIN)); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(2, 7, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + add(_descriptionLabel, gridBagConstraints); + + _iconLabel.setMinimumSize(_iconSize); + _iconLabel.setMaximumSize(_iconSize); + _iconLabel.setPreferredSize(_iconSize); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 2; + gridBagConstraints.insets = new Insets(2, 2, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHEAST; + add(_iconLabel, gridBagConstraints); + + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1.0; + add(_headerSeparator, gridBagConstraints); + } + + protected void setDescription(String description) { + _descriptionLabel.setText(description); + } + + protected void setIcon(ImageIcon icon) { + _iconLabel.setIcon(icon); + } + + protected void setTitle(String title) { + _titleLabel.setText(title); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/WizardListener.java b/installer/src/java/org/python/util/install/WizardListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardListener.java @@ -0,0 +1,13 @@ +package org.python.util.install; + +public interface WizardListener { + public void wizardCancelled(WizardEvent event); + + public void wizardFinished(WizardEvent event); + + public void wizardNext(WizardEvent event); + + public void wizardPrevious(WizardEvent event); + + public void wizardStarted(WizardEvent event); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/driver/Autotest.java b/installer/src/java/org/python/util/install/driver/Autotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Autotest.java @@ -0,0 +1,274 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; + +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.InstallationListener; +import org.python.util.install.InstallerCommandLine; +import org.python.util.install.JavaHomeHandler; +import org.python.util.install.JavaVersionTester; +import org.python.util.install.Installation.JavaVersionInfo; + +public abstract class Autotest implements InstallationListener { + + private static final String _DIR_SUFFIX = "_dir"; + + private static int _count = 0; // unique test number + private static File _rootDirectory = null; + private static JavaVersionInfo _systemDefaultJavaVersion; + + private String _name; + private File _targetDir; + private JavaHomeHandler _javaHomeHandler; + private boolean _verbose; + private String[] _commandLineArgs; + private Verifier _verifier; + + /** + * constructor + * + * @throws IOException + * @throws DriverException + */ + protected Autotest(InstallerCommandLine commandLine) throws IOException, DriverException { + _count++; + buildName(); + if (_rootDirectory == null) { + createRootDirectory(); + } + createTargetDirectory(); + setCommandLineArgs(new String[0]); // a priori value + _verbose = commandLine.hasVerboseOption(); + _javaHomeHandler = commandLine.getJavaHomeHandler(); + } + + /** + * @return the root directory for all test installations + */ + protected static File getRootDir() { + return _rootDirectory; + } + + /** + * @return the target directory of this test + */ + protected File getTargetDir() { + return _targetDir; + } + + /** + * @return the name of this test + */ + protected String getName() { + return _name; + } + + /** + * @return the array of command line arguments + */ + protected String[] getCommandLineArgs() { + return _commandLineArgs; + } + + /** + * set the array of command line arguments + * + * @param commandLineArgs + */ + protected void setCommandLineArgs(String[] commandLineArgs) { + _commandLineArgs = commandLineArgs; + } + + /** + * @return the java home handler, can be asked for deviation using isDeviation(). + */ + protected JavaHomeHandler getJavaHomeHandler() { + return _javaHomeHandler; + } + + /** + * @return true if this test should be verbose + */ + protected boolean isVerbose() { + return _verbose; + } + + /** + * @return the name suffix for this test + */ + protected abstract String getNameSuffix(); + + /** + * @throws DriverException if the target directory does not exist or is empty (installation failed) + */ + protected void assertTargetDirNotEmpty() throws DriverException { + File targetDir = getTargetDir(); + if (targetDir != null) { + if (targetDir.exists() && targetDir.isDirectory()) { + if (targetDir.listFiles().length > 0) { + return; + } + } + } + throw new DriverException("installation failed for " + targetDir.getAbsolutePath()); + } + + /** + * Convenience method to add the additional arguments, if specified behind the -A option + *

+ * This adds (if present): + *

    + *
  • target directory (should always be present) + *
  • verbose + *
  • jre + *
+ */ + protected void addAdditionalArguments() { + if (getTargetDir() != null) { + addArgument("-d"); + addArgument(getTargetDir().getAbsolutePath()); + } + if (isVerbose()) { + addArgument("-v"); + } + JavaHomeHandler javaHomeHandler = getJavaHomeHandler(); + if (javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome()) { + addArgument("-j"); + addArgument(javaHomeHandler.getHome().getAbsolutePath()); + } + } + + /** + * Add an additional String argument to the command line arguments + * + * @param newArgument + */ + protected void addArgument(String newArgument) { + setCommandLineArgs(addArgument(newArgument, getCommandLineArgs())); + } + + /** + * set the verifier + */ + protected void setVerifier(Verifier verifier) { + _verifier = verifier; + _verifier.setTargetDir(getTargetDir()); + } + + protected Verifier getVerifier() { + return _verifier; + } + + // + // private stuff + // + + /** + * build a test name containing some special characters (which will be used to create the target + * directory), and store it into _name. + */ + private void buildName() { + StringBuilder b = new StringBuilder(24); + if (_count <= 99) { + b.append('0'); + } + if (_count <= 9) { + b.append('0'); + } + b.append(_count); + // explicitly use a blank, to nail down some platform specific problems + b.append(' '); + // add an exclamation mark if possible (see issue #1208) + if(canHandleExclamationMarks()) { + b.append('!'); + } + b.append(getNameSuffix()); + b.append('_'); + _name = b.toString(); + } + + /** + * Add the new argument to the args array + * + * @param newArgument + * @param args + * + * @return the new String array, with size increased by 1 + */ + private String[] addArgument(String newArgument, String[] args) { + String[] newArgs = new String[args.length + 1]; + for (int i = 0; i < args.length; i++) { + newArgs[i] = args[i]; + } + newArgs[args.length] = newArgument; + return newArgs; + } + + /** + * create the root directory for all automatic installations + *

+ * assumed to be a subdirectory of java.io.tmpdir + * + * @throws IOException + * @throws DriverException + */ + private void createRootDirectory() throws IOException, DriverException { + File tmpFile = File.createTempFile("jython.autoinstall.root_", _DIR_SUFFIX); + if (FileHelper.createTempDirectory(tmpFile)) { + _rootDirectory = tmpFile; + } else { + throw new DriverException("unable to create root temporary directory"); + } + } + + /** + * create a target directory for a test installation + * + * @throws IOException + * @throws DriverException + */ + private void createTargetDirectory() throws IOException, DriverException { + File tmpFile = File.createTempFile(getName(), _DIR_SUFFIX, _rootDirectory); + if (FileHelper.createTempDirectory(tmpFile)) { + _targetDir = tmpFile; + } else { + throw new DriverException("unable to create temporary target directory"); + } + } + + /** + * Determine if the target directory may contain an exclamation mark (see also issue #1208). + *

+ * Autotests can handle exclamation marks, if both the running and the system default java + * specification versions are 1.6 or higher. Class.getResource() was fixed for JDK 1.6, but only if the directory name does not end with '!'... + *

+ * Currently there is no way on windows, because the enabledelayedexpansion in jython.bat cannot + * handle exclamation marks in variable names. + * + * @return true if we can handle exclamation marks, false otherwise + */ + private boolean canHandleExclamationMarks() { + boolean exclamation = false; + if (!Installation.isWindows()) { + // get the running java specification version + String specificationVersion = System.getProperty(JavaVersionTester.JAVA_SPECIFICATION_VERSION, + ""); + if (Installation.getJavaSpecificationVersion(specificationVersion) > 15) { + // get the system default java version + if (_systemDefaultJavaVersion == null) { + _systemDefaultJavaVersion = Installation.getDefaultJavaVersion(); + } + if (_systemDefaultJavaVersion.getErrorCode() == Installation.NORMAL_RETURN) { + specificationVersion = _systemDefaultJavaVersion.getSpecificationVersion(); + if (Installation.getJavaSpecificationVersion(specificationVersion) > 15) { + exclamation = true; + } + } + } + } + return exclamation; + } + +} diff --git a/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java b/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java @@ -0,0 +1,30 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.python.util.install.InstallerCommandLine; + +public class ConsoleAutotest extends SilentAutotest { + + private Collection _answers; + + protected ConsoleAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + _answers = new ArrayList(50); + } + + protected void addAnswer(String answer) { + _answers.add(answer); + } + + protected Collection getAnswers() { + return _answers; + } + + protected String getNameSuffix() { + return "consoleTest"; + } + +} diff --git a/installer/src/java/org/python/util/install/driver/ConsoleDriver.java b/installer/src/java/org/python/util/install/driver/ConsoleDriver.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/ConsoleDriver.java @@ -0,0 +1,58 @@ +package org.python.util.install.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collection; +import java.util.Iterator; + +/** + * A class driving another class, while the other class is performing console I/O. + * + *

+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * 
+ */ +public class ConsoleDriver extends Thread { + + private Tunnel _tunnel; + private Collection _answers; + + public ConsoleDriver(Tunnel tunnel, Collection answers) { + _tunnel = tunnel; + _answers = answers; + } + + /** + * Send answers in the correct sequence, as soon as the question is asked. + */ + public void run() { + Iterator answersIterator = _answers.iterator(); + while (answersIterator.hasNext()) { + String answer = (String) answersIterator.next(); + try { + readLine(); + sendAnswer(answer); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private void sendAnswer(String answer) throws IOException, InterruptedException { + Thread.sleep(100); // wait to be sure the question is really issued on the other end of the tunnel + System.out.println(" -> driving: '" + answer + "'"); + answer += Tunnel.NEW_LINE; + _tunnel.getAnswerSenderStream().write(answer.getBytes()); + _tunnel.getAnswerSenderStream().flush(); + } + + private void readLine() throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(_tunnel.getQuestionReceiverStream())); + reader.readLine(); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/DriverException.java b/installer/src/java/org/python/util/install/driver/DriverException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/DriverException.java @@ -0,0 +1,17 @@ +package org.python.util.install.driver; + +public class DriverException extends Exception { + + public DriverException() { + super(); + } + + public DriverException(String message) { + super(message); + } + + public DriverException(Throwable cause) { + super(cause); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/GuiAutotest.java b/installer/src/java/org/python/util/install/driver/GuiAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/GuiAutotest.java @@ -0,0 +1,188 @@ +package org.python.util.install.driver; + +import java.awt.AWTException; +import java.awt.Robot; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.python.util.install.InstallerCommandLine; + +public class GuiAutotest extends Autotest { + + private static final int _DEFAULT_DELAY = 500; // ms + + private Robot _robot; + private List _keyActions; + private boolean _waiting = false; + + protected GuiAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + _keyActions = new ArrayList(); + // pass in the target directory, verbositiy + String[] args = new String[] { "-d", getTargetDir().getAbsolutePath() }; + setCommandLineArgs(args); + addAdditionalArguments(); + } + + protected String getNameSuffix() { + return "guiTest"; + } + + /** + * add a normal key action (press and release) + * + * @param keyCode + */ + protected void addKeyAction(int keyCode) { + KeyAction keyAction = new KeyAction(keyCode); + addKeyAction(keyAction); + } + + /** + * add a normal key action (press and release), with a specific delay + * + * @param keyCode + * @param delay + */ + protected void addKeyAction(int keyCode, int delay) { + KeyAction keyAction = new KeyAction(keyCode, delay); + addKeyAction(keyAction); + } + + /** + * add a key action (press and release) which waits before executing + * + * @param keyCode + */ + protected void addWaitingKeyAction(int keyCode) { + KeyAction keyAction = new KeyAction(keyCode, true); + addKeyAction(keyAction); + } + + protected void setWaiting(boolean waiting) { + _waiting = waiting; + } + + /** + * execute a single gui auto test + * + * @throws DriverException + */ + protected void execute() throws DriverException { + try { + _robot = new Robot(); + + System.out.println("waiting 2 seconds for the first gui ... please do not change focus"); + _robot.delay(2000); // initial gui load + + Iterator actionsIterator = _keyActions.iterator(); + while (actionsIterator.hasNext()) { + KeyAction keyAction = (KeyAction) actionsIterator.next(); + setWaiting(keyAction.isWait()); + if (isWaiting()) { + System.out.println("waiting for the installation to finish ..."); + } + while (isWaiting()) { + try { + Thread.sleep(_DEFAULT_DELAY); + } catch (InterruptedException e) { + throw new DriverException(e); + } + } + executeKeyAction(keyAction); + } + } catch (AWTException ae) { + throw new DriverException(ae); + } + + } + + /** + * General KeyAction + */ + protected static class KeyAction { + private int _keyCode; + private int _delay; + private boolean _wait; + + /** + * @param keyCode + */ + protected KeyAction(int keyCode) { + this(keyCode, _DEFAULT_DELAY); + } + + /** + * @param keyCode + * @param delay in ms + */ + protected KeyAction(int keyCode, int delay) { + super(); + setKeyCode(keyCode); + setDelay(delay); + } + + /** + * @param keyCode + * @param wait true if we should wait before executing this key action + */ + protected KeyAction(int keyCode, boolean wait) { + this(keyCode, _DEFAULT_DELAY); + setWait(wait); + } + + protected void setKeyCode(int keyCode) { + _keyCode = keyCode; + } + + protected void setDelay(int delay) { + _delay = delay; + } + + protected int getDelay() { + return _delay; + } + + protected int getKeyCode() { + return _keyCode; + } + + protected void setWait(boolean wait) { + _wait = wait; + } + + protected boolean isWait() { + return _wait; + } + } + + // + // interface InstallationListener + // + + public void progressFinished() { + setWaiting(false); + } + + // + // private stuff + // + + private boolean isWaiting() { + return _waiting; + } + + private void addKeyAction(KeyAction keyAction) { + _keyActions.add(keyAction); + } + + private void executeKeyAction(KeyAction keyAction) { + _robot.delay(keyAction.getDelay()); + _robot.keyPress(keyAction.getKeyCode()); + _robot.delay(20); // delay was handled before press + _robot.keyRelease(keyAction.getKeyCode()); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/InstallationDriver.java b/installer/src/java/org/python/util/install/driver/InstallationDriver.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/InstallationDriver.java @@ -0,0 +1,416 @@ +package org.python.util.install.driver; + +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.python.util.install.ConsoleInstaller; +import org.python.util.install.Installation; +import org.python.util.install.InstallerCommandLine; +import org.python.util.install.JavaHomeHandler; + +public class InstallationDriver { + private SilentAutotest[] _silentTests; + private ConsoleAutotest[] _consoleTests; + private GuiAutotest[] _guiTests; + private InstallerCommandLine _commandLine; + + /** + * construct the driver + * + * @param commandLine the console arguments + * + * @throws IOException + * @throws DriverException + */ + public InstallationDriver(InstallerCommandLine commandLine) throws DriverException { + _commandLine = commandLine; + try { + buildSilentTests(); + buildConsoleTests(); + buildGuiTests(); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * execute all the automatic tests + * + * @throws DriverException + */ + public void drive() throws DriverException { + try { + // silent tests + for (int i = 0; i < _silentTests.length; i++) { + driveSilentTest(_silentTests[i]); + } + // console tests + for (int i = 0; i < _consoleTests.length; i++) { + driveConsoleTest(_consoleTests[i]); + } + // gui tests + for (int i = 0; i < _guiTests.length; i++) { + driveGuiTest(_guiTests[i]); + } + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * execute a single console test + * + * @param consoleTest + * @throws DriverException + * @throws IOException + * @throws IOException + */ + private void driveConsoleTest(ConsoleAutotest consoleTest) throws DriverException, IOException { + Tunnel _tunnel; + _tunnel = new Tunnel(); + // have to fork off the driver thread first + ConsoleDriver driver = new ConsoleDriver(_tunnel, consoleTest.getAnswers()); + driver.start(); + // now do the installation + Installation.driverMain(consoleTest.getCommandLineArgs(), consoleTest, _tunnel); + _tunnel.close(); + validate(consoleTest); + } + + /** + * execute a single silent test + * + * @param silentTest + * @throws DriverException + */ + private void driveSilentTest(SilentAutotest silentTest) throws DriverException { + Installation.driverMain(silentTest.getCommandLineArgs(), silentTest, null); // only a thin wrapper + validate(silentTest); + } + + /** + * execute a single gui test + * + * @param guiTest + * @throws DriverException + */ + private void driveGuiTest(GuiAutotest guiTest) throws DriverException { + Installation.driverMain(guiTest.getCommandLineArgs(), guiTest, null); // only a thin wrapper + guiTest.execute(); + validate(guiTest); + } + + /** + * perform validations after the test was run + * + * @param autoTest + * @throws DriverException + */ + private void validate(Autotest autoTest) throws DriverException { + autoTest.assertTargetDirNotEmpty(); + if (autoTest.getVerifier() != null) { + System.out.println("verifying installation - this can take a while ..."); + autoTest.getVerifier().verify(); + System.out.println("... installation ok.\n"); + } + } + + /** + * @return the command line the autotest session was started with + */ + private InstallerCommandLine getOriginalCommandLine() { + return _commandLine; + } + + /** + * build all the silent tests + * + * @throws IOException + * @throws DriverException + */ + private void buildSilentTests() throws IOException, DriverException { + List silentTests = new ArrayList(50); + + SilentAutotest test1 = new SilentAutotest(getOriginalCommandLine()); + String[] arguments = new String[] { "-s" }; + test1.setCommandLineArgs(arguments); + test1.addAdditionalArguments(); // this also adds target directory + test1.setVerifier(new NormalVerifier()); + silentTests.add(test1); + + SilentAutotest test2 = new SilentAutotest(getOriginalCommandLine()); + arguments = new String[] { "-s", "-t", "minimum" }; + test2.setCommandLineArgs(arguments); + test2.addAdditionalArguments(); + test2.setVerifier(new NormalVerifier()); + silentTests.add(test2); + + SilentAutotest test3 = new SilentAutotest(getOriginalCommandLine()); + arguments = new String[] { "-s", "-t", "standalone" }; + test3.setCommandLineArgs(arguments); + test3.addAdditionalArguments(); + test3.setVerifier(new StandaloneVerifier()); + silentTests.add(test3); + + // build array + int size = silentTests.size(); + _silentTests = new SilentAutotest[size]; + Iterator silentIterator = silentTests.iterator(); + for (int i = 0; i < size; i++) { + _silentTests[i] = silentIterator.next(); + } + } + + /** + * build all the console tests + * + * @throws IOException + * @throws DriverException + */ + private void buildConsoleTests() throws IOException, DriverException { + List consoleTests = new ArrayList(5); + final String[] arguments; + if (getOriginalCommandLine().hasVerboseOption()) { + arguments = new String[] { "-c", "-v" }; + } else { + arguments = new String[] { "-c" }; + } + // do NOT call addAdditionalArguments() + + ConsoleAutotest test1 = new ConsoleAutotest(getOriginalCommandLine()); + test1.setCommandLineArgs(arguments); + test1.addAnswer("e"); // language + test1.addAnswer("n"); // no read of license + test1.addAnswer("y"); // accept license + test1.addAnswer("3"); // type: minimum + test1.addAnswer("n"); // include: nothing + test1.addAnswer(test1.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test1); + test1.addAnswer("y"); // confirm copying + test1.addAnswer("n"); // no readme + test1.setVerifier(new NormalVerifier()); + consoleTests.add(test1); + + ConsoleAutotest test2 = new ConsoleAutotest(getOriginalCommandLine()); + test2.setCommandLineArgs(arguments); + test2.addAnswer("e"); // language + test2.addAnswer("n"); // no read of license + test2.addAnswer("y"); // accept license + test2.addAnswer("3"); // type: minimum + test2.addAnswer("y"); // include + test2.addAnswer("mod"); // include + test2.addAnswer("demo"); // include + test2.addAnswer("src"); // include + test2.addAnswer("n"); // no further includes + test2.addAnswer("y"); // exclude + test2.addAnswer("demo"); // exclude + test2.addAnswer("wrongAnswer"); // wrong answer + test2.addAnswer("n"); // no further excludes + test2.addAnswer(test2.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test2); + test2.addAnswer("y"); // confirm copying + test2.addAnswer("n"); // no readme + test2.setVerifier(new NormalVerifier()); + consoleTests.add(test2); + + ConsoleAutotest test3 = new ConsoleAutotest(getOriginalCommandLine()); + test3.setCommandLineArgs(arguments); + test3.addAnswer("e"); // language + test3.addAnswer("n"); // no read of license + test3.addAnswer("y"); // accept license + test3.addAnswer("9"); // type: standalone + test3.addAnswer(test3.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test3); + test3.addAnswer("y"); // confirm copying + test3.addAnswer("n"); // no readme + test3.setVerifier(new StandaloneVerifier()); + consoleTests.add(test3); + + // test for bug 1783960 + ConsoleAutotest test4 = new ConsoleAutotest(getOriginalCommandLine()); + test4.setCommandLineArgs(arguments); + test4.addAnswer("e"); // language + test4.addAnswer("n"); // no read of license + test4.addAnswer("y"); // accept license + test4.addAnswer("2"); // type: standard + test4.addAnswer("n"); // no includes + test4.addAnswer("y"); // exclude + test4.addAnswer("n"); // no further excludes + test4.addAnswer(test4.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test4); + test4.addAnswer("y"); // confirm copying + test4.addAnswer("n"); // no readme + test4.setVerifier(new NormalVerifier()); + consoleTests.add(test4); + + // build array + int size = consoleTests.size(); + _consoleTests = new ConsoleAutotest[size]; + Iterator consoleIterator = consoleTests.iterator(); + for (int i = 0; i < size; i++) { + _consoleTests[i] = consoleIterator.next(); + } + } + + private void addJavaAndOSAnswers(ConsoleAutotest test) { + JavaHomeHandler javaHomeHandler = test.getJavaHomeHandler(); + if (javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome()) { + test.addAnswer(javaHomeHandler.getHome().getAbsolutePath()); // different jre + } else { + test.addAnswer(ConsoleInstaller.CURRENT_JRE); + } + if (!Installation.isValidOs()) { + test.addAnswer(""); // enter to proceed anyway + } + } + + /** + * build all the gui tests + * + * @throws IOException + * @throws DriverException + */ + private void buildGuiTests() throws IOException, DriverException { + List guiTests = new ArrayList(50); + + if (Installation.isGuiAllowed()) { + GuiAutotest guiTest1 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest1); + // type page - use 'Standard' + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest1); + guiTest1.setVerifier(new NormalVerifier()); + guiTests.add(guiTest1); + + GuiAutotest guiTest2 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest2); + // type page - use 'All' + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_SPACE); // select 'All' + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest2); + guiTest2.setVerifier(new NormalVerifier()); + guiTests.add(guiTest2); + + GuiAutotest guiTest3 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest3); + // type page - use 'Custom' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // select 'Custom' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // deselect 'Demos and Examples' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // deselect 'Documentation' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // select 'Sources' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest3); + guiTest3.setVerifier(new NormalVerifier()); + guiTests.add(guiTest3); + + GuiAutotest guiTest4 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest4); + // type page - use 'Standalone' + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_SPACE); // select 'Standalone' + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest4); + guiTest4.setVerifier(new StandaloneVerifier()); + guiTests.add(guiTest4); + } + + // build array + int size = guiTests.size(); + _guiTests = new GuiAutotest[size]; + Iterator guiIterator = guiTests.iterator(); + for (int i = 0; i < size; i++) { + _guiTests[i] = guiIterator.next(); + } + } + + private void buildLanguageAndLicensePage(GuiAutotest guiTest) { + // language page + guiTest.addKeyAction(KeyEvent.VK_E); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // license page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); // select "i accept" + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } + + private void buildRestOfGuiPages(GuiAutotest guiTest) { + // directory page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // java selection page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + JavaHomeHandler javaHomeHandler = guiTest.getJavaHomeHandler(); + boolean isValidDeviation = javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome(); + if (isValidDeviation) { // need 2 more tabs + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + } + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // overview page + if (isValidDeviation) { + guiTest.addKeyAction(KeyEvent.VK_SPACE, 3000); // enough time to check the java version + } else { + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } + // installation page (skipped) + // readme page + guiTest.addWaitingKeyAction(KeyEvent.VK_TAB); // wait for the installation to finish + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // success page + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } +} diff --git a/installer/src/java/org/python/util/install/driver/NormalVerifier.java b/installer/src/java/org/python/util/install/driver/NormalVerifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/NormalVerifier.java @@ -0,0 +1,287 @@ +package org.python.util.install.driver; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.MessageFormat; +import java.util.StringTokenizer; + +import org.python.util.install.ChildProcess; +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.JavaHomeHandler; + +public class NormalVerifier implements Verifier { + + protected static final String AUTOTEST_PY = "autotest.py"; + + protected static final String JYTHON_TEST = "jython_test"; + + private static final String BIN = "bin"; + + private static final String BAT_EXTENSION = ".bat"; + + private static final String JYTHON_UP = "jython up and running!"; + + private static final String JYTHON = "jython"; + + private static final String TEMPLATE_SUFFIX = ".template"; + + private static final String VERIFYING = "verifying"; + + private File _targetDir; + + public void setTargetDir(File targetDir) { + _targetDir = targetDir; + } + + public File getTargetDir() { + return _targetDir; + } + + public void verify() throws DriverException { + createTestScriptFile(); // create the test .py script + // verify the most simple start of jython works + verifyStart(getSimpleCommand()); + if (doShellScriptTests()) { + // verify more complex versions of starting jython + verifyStart(getShellScriptTestCommand()); + } + } + + /** + * Will be overridden in subclass StandaloneVerifier + * + * @return the command array to start jython with + * @throws DriverException + * if there was a problem getting the target directory path + */ + protected String[] getSimpleCommand() throws DriverException { + String parentDirName = null; + try { + parentDirName = getTargetDir().getCanonicalPath() + File.separator; + } catch (IOException ioe) { + throw new DriverException(ioe); + } + String[] command = new String[2]; + if (Installation.isWindows()) { + command[0] = parentDirName + JYTHON + BAT_EXTENSION; + } else { + command[0] = parentDirName + JYTHON; + } + command[1] = parentDirName + AUTOTEST_PY; + return command; + } + + /** + * @return The command to test the shell script more deeply + * @throws DriverException + */ + protected final String[] getShellScriptTestCommand() throws DriverException { + // first we have to create the shell script + File testCommandDir = getShellScriptTestCommandDir(); + if (!testCommandDir.exists()) { + if (!testCommandDir.mkdirs()) { + throw new DriverException("unable to create directory " + + testCommandDir.getAbsolutePath()); + } + } + String commandName = JYTHON_TEST; + boolean isWindows = Installation.isWindows(); + if (isWindows) { + commandName = commandName.concat(BAT_EXTENSION); + } + File command = new File(testCommandDir, commandName); + try { + if (!command.exists()) { + command.createNewFile(); + } + FileHelper.write(command, getShellScriptTestContents()); + if (!isWindows) { + FileHelper.makeExecutable(command); + } + return new String[] {command.getCanonicalPath()}; + } catch (Exception e) { + throw new DriverException(e); + } + } + + /** + * @return The contents of the shell test script + * @throws DriverException + */ + protected final String getShellScriptTestContents() throws DriverException { + String contents = ""; + String templateName = JYTHON_TEST; + if (Installation.isWindows()) { + templateName = templateName.concat(BAT_EXTENSION); + } + templateName = templateName.concat(TEMPLATE_SUFFIX); + InputStream inputStream = FileHelper.getRelativeURLAsStream(getClass(), templateName); + if (inputStream != null) { + try { + String template = FileHelper.readAll(inputStream); + String targetDirPath = getTargetDir().getCanonicalPath(); + String upScriptPath = getSimpleCommand()[1]; + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(); + String javaHomeString = ""; + if (javaHomeHandler.isValidHome()) { + javaHomeString = javaHomeHandler.getHome().getAbsolutePath(); + } + contents = MessageFormat.format(template, + targetDirPath, + upScriptPath, + javaHomeString, + VERIFYING); + } catch (Exception e) { + throw new DriverException(e); + } + } + return contents; + } + + /** + * @return The directory where to create the shell script test command in. + * + * @throws DriverException + */ + protected final File getShellScriptTestCommandDir() throws DriverException { + String dirName; + try { + dirName = getTargetDir().getCanonicalPath().concat(File.separator).concat(BIN); + return new File(dirName); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * Internal method verifying a jython-starting command by capturing the ouptut + * + * @param command + * + * @throws DriverException + */ + private void verifyStart(String[] command) throws DriverException { + ChildProcess childProcess = new ChildProcess(command); + childProcess.setDebug(true); + ByteArrayOutputStream redirectedErr = new ByteArrayOutputStream(); + ByteArrayOutputStream redirectedOut = new ByteArrayOutputStream(); + int exitValue = 0; + PrintStream oldErr = System.err; + PrintStream oldOut = System.out; + try { + System.setErr(new PrintStream(redirectedErr)); + System.setOut(new PrintStream(redirectedOut)); + exitValue = childProcess.run(); + } finally { + System.setErr(oldErr); + System.setOut(oldOut); + } + // verify the output + String output = null; + String error = null; + try { + redirectedErr.flush(); + redirectedOut.flush(); + String encoding = "US-ASCII"; + output = redirectedOut.toString(encoding); + error = redirectedErr.toString(encoding); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + if (exitValue != 0) { + throw new DriverException("start of jython failed, output:\n" + output + "\nerror:\n" + + error); + } + verifyError(error); + verifyOutput(output); + } + + /** + * Will be overridden in subclass StandaloneVerifier + * + * @return true if the jython start shell script should be verified (using + * different options) + */ + protected boolean doShellScriptTests() { + return true; + } + + private void verifyError(String error) throws DriverException { + StringTokenizer tokenizer = new StringTokenizer(error, "\n"); + while (tokenizer.hasMoreTokens()) { + String line = tokenizer.nextToken(); + if (isExpectedError(line)) { + feedback(line); + } else { + throw new DriverException(error); + } + } + } + + private boolean isExpectedError(String line) { + boolean expected = false; + if (line.startsWith("*sys-package-mgr*")) { + expected = true; + } else if (line.indexOf("32 bit") >= 0 && line.indexOf("64 bit") >= 0) { + // OS X incompatibility message when using -A -j java1.6.0 from java1.5.0 + expected = true; + } + return expected; + } + + private void verifyOutput(String output) throws DriverException { + boolean started = false; + StringTokenizer tokenizer = new StringTokenizer(output, "\n"); + while (tokenizer.hasMoreTokens()) { + String line = tokenizer.nextToken(); + if (isExpectedOutput(line)) { + feedback(line); + if (line.startsWith(JYTHON_UP)) { + started = true; + } + } else { + throw new DriverException(output); + } + } + if (!started) { + throw new DriverException("start of jython failed:\n" + output); + } + } + + private boolean isExpectedOutput(String line) { + boolean expected = false; + if (line.startsWith("[ChildProcess]") || line.startsWith(VERIFYING)) { + expected = true; + } else if (line.startsWith(JYTHON_UP)) { + expected = true; + } + return expected; + } + + private String getTestScript() { + StringBuilder b = new StringBuilder(80); + b.append("import sys\n"); + b.append("import os\n"); + b.append("print '"); + b.append(JYTHON_UP); + b.append("'\n"); + return b.toString(); + } + + private void createTestScriptFile() throws DriverException { + File file = new File(getTargetDir(), AUTOTEST_PY); + try { + FileHelper.write(file, getTestScript()); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + private void feedback(String line) { + System.out.println(line); + } +} diff --git a/installer/src/java/org/python/util/install/driver/SilentAutotest.java b/installer/src/java/org/python/util/install/driver/SilentAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/SilentAutotest.java @@ -0,0 +1,25 @@ +package org.python.util.install.driver; + +import java.io.IOException; + +import org.python.util.install.InstallerCommandLine; + +public class SilentAutotest extends Autotest { + + protected SilentAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + } + + protected String getNameSuffix() { + return "silentTest"; + } + + // + // interface InstallationListener + // + + public void progressFinished() { + // ignored + } + +} diff --git a/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java b/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java @@ -0,0 +1,74 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.python.util.install.JavaHomeHandler; + +public class StandaloneVerifier extends NormalVerifier { + + public void verify() throws DriverException { + // make sure only JYTHON_JAR is in the target directory + if (getTargetDir().listFiles().length > 1) { + throw new DriverException("more than " + JYTHON_JAR + " installed"); + } + // make sure JYTHON_JAR contains a MANIFEST and a /Lib directory + verifyJythonJar(); + // do the jython startup verification from the superclass + super.verify(); + } + + @Override + protected String[] getSimpleCommand() throws DriverException { + String parentDirName = null; + try { + parentDirName = getTargetDir().getCanonicalPath() + File.separator; + } catch (IOException ioe) { + throw new DriverException(ioe); + } + String command[] = new String[4]; + command[0] = new JavaHomeHandler().getExecutableName(); + command[1] = "-jar"; + command[2] = parentDirName + JYTHON_JAR; + command[3] = parentDirName + AUTOTEST_PY; + return command; + } + + @Override + protected boolean doShellScriptTests() { + return false; + } + + private void verifyJythonJar() throws DriverException { + File jythonJar = getTargetDir().listFiles()[0]; + JarFile jar = null; + try { + jar = new JarFile(jythonJar); + if (jar.getManifest() == null) { + throw new DriverException(JYTHON_JAR + " contains no MANIFEST"); + } + boolean hasLibDir = false; + Enumeration entries = jar.entries(); + while (!hasLibDir && entries.hasMoreElements()) { + JarEntry entry = (JarEntry)entries.nextElement(); + if (entry.getName().startsWith("Lib/")) { + hasLibDir = true; + } + } + if (!hasLibDir) { + throw new DriverException(JYTHON_JAR + " contains no /Lib directory"); + } + } catch (IOException e) { + throw new DriverException(e); + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException ioe) {} + } + } + } +} diff --git a/installer/src/java/org/python/util/install/driver/Tunnel.java b/installer/src/java/org/python/util/install/driver/Tunnel.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Tunnel.java @@ -0,0 +1,61 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +/** + * A communication tunnel between a console driver and a console. + * + *
+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * 
+ */ +public class Tunnel { + + public static final String NEW_LINE = "\n"; + + private PipedOutputStream _questionSenderStream; + private PipedInputStream _questionReceiverStream; + private PipedOutputStream _answerSenderStream; + private PipedInputStream _answerReceiverStream; + + public Tunnel() throws IOException { + _questionSenderStream = new PipedOutputStream(); + _questionReceiverStream = new PipedInputStream(); + _questionSenderStream.connect(_questionReceiverStream); + + _answerSenderStream = new PipedOutputStream(); + _answerReceiverStream = new PipedInputStream(); + _answerSenderStream.connect(_answerReceiverStream); + } + + public PipedOutputStream getQuestionSenderStream() { + return _questionSenderStream; + } + + public PipedInputStream getQuestionReceiverStream() { + return _questionReceiverStream; + } + + public PipedOutputStream getAnswerSenderStream() { + return _answerSenderStream; + } + + public PipedInputStream getAnswerReceiverStream() { + return _answerReceiverStream; + } + + public void close() throws IOException { + _questionReceiverStream.close(); + _questionSenderStream.close(); + _answerReceiverStream.close(); + _answerSenderStream.close(); + + _questionReceiverStream = null; + _questionSenderStream = null; + _answerReceiverStream = null; + _answerSenderStream = null; + } +} diff --git a/installer/src/java/org/python/util/install/driver/Verifier.java b/installer/src/java/org/python/util/install/driver/Verifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Verifier.java @@ -0,0 +1,17 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.JarInstaller; + +public interface Verifier { + + public static final String JYTHON_JAR = JarInstaller.JYTHON_JAR; + + public void setTargetDir(File targetDir); + + public File getTargetDir(); + + public void verify() throws DriverException; + +} diff --git a/installer/src/java/org/python/util/install/driver/jython_test.bat.template b/installer/src/java/org/python/util/install/driver/jython_test.bat.template new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/jython_test.bat.template @@ -0,0 +1,73 @@ + at echo off + +rem 3 variables to be set from the caller, UNquoted: +set _INSTALL_DIR={0} +set _SCRIPT={1} +set _JAVA_HOME={2} + +rem save old home env vars and classpath: +set _OLD_JAVA_HOME=%JAVA_HOME% +set _OLD_JYTHON_HOME=%JYTHON_HOME% +set _OLD_CLASSPATH=%CLASSPATH% + +cd /d "%_INSTALL_DIR%\bin" + +echo {3}: only JAVA_HOME, quoted: +set JAVA_HOME="%_JAVA_HOME%" +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: only JAVA_HOME, UNquoted: +set JAVA_HOME=%_JAVA_HOME% +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: both homes quoted: +set JAVA_HOME="%_JAVA_HOME%" +set JYTHON_HOME="%_INSTALL_DIR%" +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: both homes UNquoted: +set JAVA_HOME=%_JAVA_HOME% +set JYTHON_HOME=%_INSTALL_DIR% +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +cd .. + +echo {3}: no home, calling in home dir: +set JAVA_HOME= +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +cd .. + +echo {3}: no home, calling /jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +call "%_INSTALL_DIR%\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: no home, calling bin/jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +call "%_INSTALL_DIR%\bin\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: no home, setting CLASSPATH, calling /jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +set CLASSPATH=.;C:\Program Files (x86)\Java\jre6\lib\ext\QTJava.zip +call "%_INSTALL_DIR%\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +rem cleanup: +set JAVA_HOME=%_OLD_JAVA_HOME% +set JYTHON_HOME=%_OLD_JYTHON_HOME% +set CLASSPATH=%_OLD_CLASSPATH% +cd /d "%~dp0%" +exit /b %E% diff --git a/installer/src/java/org/python/util/install/driver/jython_test.template b/installer/src/java/org/python/util/install/driver/jython_test.template new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/jython_test.template @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# 3 variables to be set from the caller (unquoted) -> quoted in here: +_INSTALL_DIR="{0}" +_SCRIPT="{1}" +_JAVA_HOME="{2}" + +# save old home env vars: +_OLD_JAVA_HOME=$JAVA_HOME +_OLD_JYTHON_HOME=$JYTHON_HOME +# save current dir +_CURDIR=`pwd` + +cd "$_INSTALL_DIR/bin" + +echo "{3}: no home:" +export JAVA_HOME= +export JYTHON_HOME= +./jython "$_SCRIPT" + +echo "{3}: only JAVA_HOME:" +export JAVA_HOME="$_JAVA_HOME" +export JYTHON_HOME= +./jython "$_SCRIPT" + +echo "{3}: only JYTHON_HOME:" +export JAVA_HOME= +export JYTHON_HOME="$_INSTALL_DIR" +./jython "$_SCRIPT" + +echo "{3}: both homes:" +export JAVA_HOME="$_JAVA_HOME" +export JYTHON_HOME="$_INSTALL_DIR" +./jython "$_SCRIPT" + +cd .. + +echo "{3}: no home, calling in home dir:" +export JAVA_HOME= +export JYTHON_HOME= +./jython "$_SCRIPT" + +cd ~ + +echo "{3}: no home, calling /jython from another working dir:" +export JAVA_HOME= +export JYTHON_HOME= +"$_INSTALL_DIR/jython" "$_SCRIPT" + +echo "{3}: no home, calling /bin/jython from another working dir:" +export JAVA_HOME= +export JYTHON_HOME= +"$_INSTALL_DIR/bin/jython" "$_SCRIPT" + +# cleanup: +cd "$_CURDIR" +export JAVA_HOME=$_OLD_JAVA_HOME +export JYTHON_HOME=$_OLD_JYTHON_HOME diff --git a/installer/src/java/org/python/util/install/jython_small_c.png b/installer/src/java/org/python/util/install/jython_small_c.png new file mode 100644 index 0000000000000000000000000000000000000000..e65a7f18e5ecad694dd2093cf01256a2eb93aada GIT binary patch [stripped] diff --git a/installer/test/java/org/AllTests.java b/installer/test/java/org/AllTests.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/AllTests.java @@ -0,0 +1,101 @@ +package org; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * A package recursive test suite. + *

+ * All classes ending with 'Test' are added to the suite. + * + * @see AllTests.TestClassFilter + */ +public class AllTests extends TestSuite { + + /** + * @return Test suite at the directory where this class resists + * + * @throws Exception + */ + public static Test suite() throws Exception { + Class suiteClass = AllTests.class; + String testSuiteClassName = suiteClass.getName(); + File suiteFile = new File(suiteClass.getClassLoader().getResource( + testSuiteClassName.replace('.', '/').concat(".class")).getFile()); + String basePackage = suiteClass.getPackage().getName(); + File baseDir = suiteFile.getParentFile(); + TestSuite suite = new TestSuite("Test " + basePackage + " recursive."); + buildSuite(baseDir.getAbsolutePath().length(), basePackage, baseDir, new TestClassFilter(), suite); + return suite; + } + + // + // private methods + // + + private static void buildSuite(int prefixLength, String basePackage, File currentDir, FilenameFilter filter, + TestSuite currentSuite) throws Exception { + List potentialDirectories = Arrays.asList(currentDir.listFiles(filter)); + if (potentialDirectories.size() == 0) { + return; + } + StringBuffer currentPackageName = new StringBuffer(200); + currentPackageName.append(basePackage); + currentPackageName.append(currentDir.getAbsolutePath().substring(prefixLength).replace('\\', '.').replace('/', + '.')); + + List classFiles = new ArrayList(potentialDirectories.size()); + Collections.sort(potentialDirectories, new FileComparator()); + Iterator directoryIterator = potentialDirectories.iterator(); + while (directoryIterator.hasNext()) { + File potentialDirectory = (File) directoryIterator.next(); + if (potentialDirectory.isDirectory()) { + TestSuite subTestSuite = new TestSuite(potentialDirectory.getName()); + buildSuite(prefixLength, basePackage, potentialDirectory, filter, subTestSuite); + // only if suite contains tests + if (subTestSuite.countTestCases() > 0) { + currentSuite.addTest(subTestSuite); + } + } else { + classFiles.add(potentialDirectory); + } + } + Iterator fileIterator = classFiles.iterator(); + while (fileIterator.hasNext()) { + File file = (File) fileIterator.next(); + StringBuffer className = new StringBuffer(200); + className.append(currentPackageName); + className.append('.'); + String fileName = file.getName(); + className.append(fileName); + className.setLength(className.length() - 6); + currentSuite.addTest(new TestSuite(Class.forName(className.toString()), fileName.substring(0, fileName + .length() - 6))); + } + } + + private static class TestClassFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + if (name.endsWith("Test.class")) { + return true; + } + return new File(dir, name).isDirectory(); + } + } + + private static class FileComparator implements Comparator { + public int compare(File f1, File f2) { + return f1.getAbsolutePath().compareTo(f2.getAbsolutePath()); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/ApplicationTest.java b/installer/test/java/org/apache/commons/cli/ApplicationTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ApplicationTest.java @@ -0,0 +1,120 @@ +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + *

+ * This is a collection of tests that test real world + * applications command lines. + *

+ * + *

+ * The following are the applications that are tested: + *

    + *
  • Ant
  • + *
+ *

+ * + * @author John Keyes (john at integralsource.com) + */ +public class ApplicationTest extends TestCase { + + public static Test suite() { + return new TestSuite(ApplicationTest.class); + } + + public ApplicationTest(String name) + { + super(name); + } + + /** + * + */ + public void testLs() { + // create the command line parser + CommandLineParser parser = new PosixParser(); + Options options = new Options(); + options.addOption( "a", "all", false, "do not hide entries starting with ." ); + options.addOption( "A", "almost-all", false, "do not list implied . and .." ); + options.addOption( "b", "escape", false, "print octal escapes for nongraphic characters" ); + OptionBuilder.withLongOpt( "block-size" ); + OptionBuilder.withDescription( "use SIZE-byte blocks" ); + OptionBuilder.withValueSeparator( '=' ); + OptionBuilder.hasArg(); + options.addOption(OptionBuilder.create()); +// options.addOption( OptionBuilder.withLongOpt( "block-size" ) +// .withDescription( "use SIZE-byte blocks" ) +// .withValueSeparator( '=' ) +// .hasArg() +// .create() ); + options.addOption( "B", "ignore-backups", false, "do not list implied entried ending with ~"); + options.addOption( "c", false, "with -lt: sort by, and show, ctime (time of last modification of file status information) with -l:show ctime and sort by name otherwise: sort by ctime" ); + options.addOption( "C", false, "list entries by columns" ); + + String[] args = new String[]{ "--block-size=10" }; + + try { + CommandLine line = parser.parse( options, args ); + assertTrue( line.hasOption( "block-size" ) ); + assertEquals( line.getOptionValue( "block-size" ), "10" ); + } + catch( ParseException exp ) { + fail( "Unexpected exception:" + exp.getMessage() ); + } + } + + /** + * Ant test + */ + public void testAnt() { + // use the GNU parser + CommandLineParser parser = new GnuParser( ); + Options options = new Options(); + options.addOption( "help", false, "print this message" ); + options.addOption( "projecthelp", false, "print project help information" ); + options.addOption( "version", false, "print the version information and exit" ); + options.addOption( "quiet", false, "be extra quiet" ); + options.addOption( "verbose", false, "be extra verbose" ); + options.addOption( "debug", false, "print debug information" ); + options.addOption( "version", false, "produce logging information without adornments" ); + options.addOption( "logfile", true, "use given file for log" ); + options.addOption( "logger", true, "the class which is to perform the logging" ); + options.addOption( "listener", true, "add an instance of a class as a project listener" ); + options.addOption( "buildfile", true, "use given buildfile" ); + OptionBuilder.withDescription( "use value for given property" ); + OptionBuilder.hasArgs(); + OptionBuilder.withValueSeparator(); + options.addOption( OptionBuilder.create( 'D' ) ); + //, null, true, , false, true ); + options.addOption( "find", true, "search for buildfile towards the root of the filesystem and use it" ); + + String[] args = new String[]{ "-buildfile", "mybuild.xml", + "-Dproperty=value", "-Dproperty1=value1", + "-projecthelp" }; + + try { + CommandLine line = parser.parse( options, args ); + + // check multiple values + String[] opts = line.getOptionValues( "D" ); + assertEquals( "property", opts[0] ); + assertEquals( "value", opts[1] ); + assertEquals( "property1", opts[2] ); + assertEquals( "value1", opts[3] ); + + // check single value + assertEquals( line.getOptionValue( "buildfile"), "mybuild.xml" ); + + // check option + assertTrue( line.hasOption( "projecthelp") ); + } + catch( ParseException exp ) { + fail( "Unexpected exception:" + exp.getMessage() ); + } + + } + +} \ No newline at end of file diff --git a/installer/test/java/org/apache/commons/cli/BugsTest.java b/installer/test/java/org/apache/commons/cli/BugsTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/BugsTest.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: BugsTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class BugsTest extends TestCase +{ + + public static Test suite() { + return new TestSuite( BugsTest.class ); + } + + public BugsTest( String name ) + { + super( name ); + } + + public void setUp() + { + } + + public void tearDown() + { + } + + public void test11457() { + Options options = new Options(); + OptionBuilder.withLongOpt( "verbose" ); + options.addOption( OptionBuilder.create() ); + String[] args = new String[] { "--verbose" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertTrue( cmd.hasOption( "verbose" ) ); + } + catch( ParseException exp ) { + exp.printStackTrace(); + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test11458() + { + Options options = new Options(); + OptionBuilder.withValueSeparator( '=' ); + OptionBuilder.hasArgs(); + options.addOption(OptionBuilder.create( 'D' ) ); + OptionBuilder.withValueSeparator( ':' ); + OptionBuilder.hasArgs(); + options.addOption( OptionBuilder.create( 'p' ) ); + String[] args = new String[] { "-DJAVA_HOME=/opt/java" , + "-pfile1:file2:file3" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + + String[] values = cmd.getOptionValues( 'D' ); + + assertEquals( values[0], "JAVA_HOME" ); + assertEquals( values[1], "/opt/java" ); + + values = cmd.getOptionValues( 'p' ); + + assertEquals( values[0], "file1" ); + assertEquals( values[1], "file2" ); + assertEquals( values[2], "file3" ); + + java.util.Iterator iter = cmd.iterator(); + while( iter.hasNext() ) { + Option opt = (Option)iter.next(); + switch( opt.getId() ) { + case 'D': + assertEquals( opt.getValue( 0 ), "JAVA_HOME" ); + assertEquals( opt.getValue( 1 ), "/opt/java" ); + break; + case 'p': + assertEquals( opt.getValue( 0 ), "file1" ); + assertEquals( opt.getValue( 1 ), "file2" ); + assertEquals( opt.getValue( 2 ), "file3" ); + break; + default: + fail( "-D option not found" ); + } + } + } + catch( ParseException exp ) { + fail( "Unexpected Exception:\nMessage:" + exp.getMessage() + + "Type: " + exp.getClass().getName() ); + } + } + + public void test11680() + { + Options options = new Options(); + options.addOption("f", true, "foobar"); + options.addOption("m", true, "missing"); + String[] args = new String[] { "-f" , "foo" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + + try { + cmd.getOptionValue( "f", "default f"); + cmd.getOptionValue( "m", "default m"); + } + catch( NullPointerException exp ) { + fail( "NullPointer caught: " + exp.getMessage() ); + } + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test11456() + { + // Posix + Options options = new Options(); + OptionBuilder.hasOptionalArg(); + options.addOption( OptionBuilder.create( 'a' ) ); + OptionBuilder.hasArg(); + options.addOption( OptionBuilder.create( 'b' ) ); + String[] args = new String[] { "-a", "-bvalue" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertEquals( cmd.getOptionValue( 'b' ), "value" ); + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + + // GNU + options = new Options(); + OptionBuilder.hasOptionalArg(); + options.addOption( OptionBuilder.create( 'a' ) ); + OptionBuilder.hasArg(); + options.addOption( OptionBuilder.create( 'b' ) ); + args = new String[] { "-a", "-b", "value" }; + + parser = new GnuParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertEquals( cmd.getOptionValue( 'b' ), "value" ); + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + + } + + public void test12210() { + // create the main options object which will handle the first parameter + Options mainOptions = new Options(); + // There can be 2 main exclusive options: -exec|-rep + + // Therefore, place them in an option group + + String[] argv = new String[] { "-exec", "-exec_opt1", "-exec_opt2" }; + OptionGroup grp = new OptionGroup(); + + grp.addOption(new Option("exec",false,"description for this option")); + + grp.addOption(new Option("rep",false,"description for this option")); + + mainOptions.addOptionGroup(grp); + + // for the exec option, there are 2 options... + Options execOptions = new Options(); + execOptions.addOption("exec_opt1",false," desc"); + execOptions.addOption("exec_opt2",false," desc"); + + // similarly, for rep there are 2 options... + Options repOptions = new Options(); + repOptions.addOption("repopto",false,"desc"); + repOptions.addOption("repoptt",false,"desc"); + + // create the parser + GnuParser parser = new GnuParser(); + + // finally, parse the arguments: + + // first parse the main options to see what the user has specified + // We set stopAtNonOption to true so it does not touch the remaining + // options + try { + CommandLine cmd = parser.parse(mainOptions,argv,true); + // get the remaining options... + argv = cmd.getArgs(); + + if(cmd.hasOption("exec")){ + cmd = parser.parse(execOptions,argv,false); + // process the exec_op1 and exec_opt2... + assertTrue( cmd.hasOption("exec_opt1") ); + assertTrue( cmd.hasOption("exec_opt2") ); + } + else if(cmd.hasOption("rep")){ + cmd = parser.parse(repOptions,argv,false); + // process the rep_op1 and rep_opt2... + } + else { + fail( "exec option not found" ); + } + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + } + + public void test13425() { + Options options = new Options(); + OptionBuilder.withLongOpt( "old-password" ); + OptionBuilder.withDescription( "Use this option to specify the old password" ); + OptionBuilder.hasArg(); + Option oldpass = OptionBuilder.create( 'o' ); + OptionBuilder.withLongOpt( "new-password" ); + OptionBuilder.withDescription( "Use this option to specify the new password" ); + OptionBuilder.hasArg(); + Option newpass = OptionBuilder.create( 'n' ); + + String[] args = { + "-o", + "-n", + "newpassword" + }; + + options.addOption( oldpass ); + options.addOption( newpass ); + + Parser parser = new PosixParser(); + + CommandLine line = null; + try { + line = parser.parse( options, args ); + } + // catch the exception and leave the method + catch( Exception exp ) { + assertTrue( exp != null ); + return; + } + fail( "MissingArgumentException not caught." + line); + } + + public void test13666() { + Options options = new Options(); + OptionBuilder.withDescription( "dir" ); + OptionBuilder.hasArg(); + Option dir = OptionBuilder.create( 'd' ); + options.addOption( dir ); + try { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp( "dir", options ); + } + catch( Exception exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test13935() { + OptionGroup directions = new OptionGroup(); + + Option left = new Option( "l", "left", false, "go left" ); + Option right = new Option( "r", "right", false, "go right" ); + Option straight = new Option( "s", "straight", false, "go straight" ); + Option forward = new Option( "f", "forward", false, "go forward" ); + forward.setRequired( true ); + + directions.addOption( left ); + directions.addOption( right ); + directions.setRequired( true ); + + Options opts = new Options(); + opts.addOptionGroup( directions ); + opts.addOption( straight ); + + CommandLineParser parser = new PosixParser(); + boolean exception = false; + + CommandLine line = null; + String[] args = new String[] { }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + exception = true; + } + + if( !exception ) { + fail( "Expected exception not caught."); + } + + exception = false; + + args = new String[] { "-s" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + exception = true; + } + + if( !exception ) { + fail( "Expected exception not caught."); + } + + exception = false; + + args = new String[] { "-s", "-l" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + + opts.addOption( forward ); + args = new String[] { "-s", "-l", "-f" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + if(line != null) { + line = null; + } + } +} diff --git a/installer/test/java/org/apache/commons/cli/BuildTest.java b/installer/test/java/org/apache/commons/cli/BuildTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/BuildTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: BuildTest.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class BuildTest extends TestCase +{ + + public static Test suite() { + return new TestSuite(BuildTest.class); + } + + public BuildTest(String name) + { + super(name); + } + + public void setUp() + { + + } + + public void tearDown() + { + + } + + public void testSimple() + { + Options opts = new Options(); + + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "toggle -b"); + } + + public void testDuplicateSimple() + { + Options opts = new Options(); + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("a", + true, + "toggle -a*"); + + assertEquals( "last one in wins", "toggle -a*", opts.getOption("a").getDescription() ); + } + + public void testLong() + { + Options opts = new Options(); + + opts.addOption("a", + "--a", + false, + "toggle -a"); + + opts.addOption("b", + "--b", + true, + "set -b"); + + } + + public void testDuplicateLong() + { + Options opts = new Options(); + opts.addOption("a", + "--a", + false, + "toggle -a"); + + opts.addOption("a", + "--a", + false, + "toggle -a*"); + assertEquals( "last one in wins", "toggle -a*", opts.getOption("a").getDescription() ); + } +} diff --git a/installer/test/java/org/apache/commons/cli/GnuParseTest.java b/installer/test/java/org/apache/commons/cli/GnuParseTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/GnuParseTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: GnuParseTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class GnuParseTest extends TestCase +{ + private Options _options = null; + private CommandLineParser _parser = null; + + public static Test suite() { + return new TestSuite( GnuParseTest.class ); + } + + public GnuParseTest( String name ) + { + super( name ); + } + + public void setUp() + { + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption("b", + "bfile", + true, + "set the value of [b]") + .addOption("c", + "copt", + false, + "turn [c] on or off"); + + _parser = new GnuParser( ); + } + + public void tearDown() + { + + } + + public void testSimpleShort() + { + String[] args = new String[] { "-a", + "-b", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSimpleLong() + { + String[] args = new String[] { "--enable-a", + "--bfile", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testExtraOption() + { + String[] args = new String[] { "-a", "-d", "-b", "toast", + "foo", "bar" }; + + boolean caught = false; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 3); + } + catch (UnrecognizedOptionException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + assertTrue( "Confirm UnrecognizedOptionException caught", caught ); + } + + public void testMissingArg() + { + + String[] args = new String[] { "-b" }; + + boolean caught = false; + + CommandLine cl = null; + try + { + cl = _parser.parse(_options, args); + } + catch (MissingArgumentException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + + assertTrue( "Confirm MissingArgumentException caught " + cl, caught ); + } + + public void testStop() + { + String[] args = new String[] { "-c", + "foober", + "-b", + "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultiple() + { + String[] args = new String[] { "-c", + "foobar", + "-b", + "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultipleWithLong() + { + String[] args = new String[] { "--copt", + "foobar", + "--bfile", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options,args, + true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testDoubleDash() + { + String[] args = new String[] { "--copt", + "--", + "-b", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm -b is not set", ! cl.hasOption("b") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleDash() + { + String[] args = new String[] { "--copt", + "-b", "-", + "-a", + "-" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("-") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("-") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + + } +} diff --git a/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java b/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: HelpFormatterExamples.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ +package org.apache.commons.cli; + +/** + * A sample program shpwing the use of Options and the HelpFormatter class + * + * @author Slawek Zachcial + **/ +public class HelpFormatterExamples +{ + // --------------------------------------------------------------- Constants + + // ------------------------------------------------------------------ Static + + public static void main( String[] args ) + { + System.out.println("\n#\n# 'man' example\n#"); + manExample(); +/* + System.out.println("\n#\n# 'bzip2' example\n#"); + bzip2Example(); + System.out.println("\n#\n# 'ls' example\n#"); + lsExample(); +*/ + } + + static void manExample() + { + String cmdLine = + "man [-c|-f|-k|-w|-tZT device] [-adlhu7V] [-Mpath] [-Ppager] [-Slist] " + + "[-msystem] [-pstring] [-Llocale] [-eextension] [section] page ..."; + Options opts = + new Options(). + addOption("a", "all", false, "find all matching manual pages."). + addOption("d", "debug", false, "emit debugging messages."). + addOption("e", "extension", false, "limit search to extension type 'extension'."). + addOption("f", "whatis", false, "equivalent to whatis."). + addOption("k", "apropos", false, "equivalent to apropos."). + addOption("w", "location", false, "print physical location of man page(s)."). + addOption("l", "local-file", false, "interpret 'page' argument(s) as local filename(s)"). + addOption("u", "update", false, "force a cache consistency check."). + //FIXME - should generate -r,--prompt string + addOption("r", "prompt", true, "provide 'less' pager with prompt."). + addOption("c", "catman", false, "used by catman to reformat out of date cat pages."). + addOption("7", "ascii", false, "display ASCII translation or certain latin1 chars."). + addOption("t", "troff", false, "use troff format pages."). + //FIXME - should generate -T,--troff-device device + addOption("T", "troff-device", true, "use groff with selected device."). + addOption("Z", "ditroff", false, "use groff with selected device."). + addOption("D", "default", false, "reset all options to their default values."). + //FIXME - should generate -M,--manpath path + addOption("M", "manpath", true, "set search path for manual pages to 'path'."). + //FIXME - should generate -P,--pager pager + addOption("P", "pager", true, "use program 'pager' to display output."). + //FIXME - should generate -S,--sections list + addOption("S", "sections", true, "use colon separated section list."). + //FIXME - should generate -m,--systems system + addOption("m", "systems", true, "search for man pages from other unix system(s)."). + //FIXME - should generate -L,--locale locale + addOption("L", "locale", true, "defaine the locale for this particular man search."). + //FIXME - should generate -p,--preprocessor string + addOption("p", "preprocessor", true, "string indicates which preprocessor to run.\n" + + " e - [n]eqn p - pic t - tbl\n" + + " g - grap r - refer v - vgrind"). + addOption("V", "version", false, "show version."). + addOption("h", "help", false, "show this usage message."); + + HelpFormatter hf = new HelpFormatter(); + //hf.printHelp(cmdLine, opts); + hf.printHelp(60, cmdLine, null, opts, null); + } + + static void bzip2Example() + { + System.out.println( "Coming soon" ); + } + + static void lsExample() + { + System.out.println( "Coming soon" ); + } + + + // -------------------------------------------------------------- Attributes + + // ------------------------------------------------------------ Constructors + + // ------------------------------------------------------------------ Public + + // --------------------------------------------------------------- Protected + + // ------------------------------------------------------- Package protected + + // ----------------------------------------------------------------- Private + + // ----------------------------------------------------------- Inner classes + +} diff --git a/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java b/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java @@ -0,0 +1,82 @@ +package org.apache.commons.cli; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +import junit.framework.TestCase; + +public class HelpFormatterTest extends TestCase { + + private Options _options; + + protected void setUp() { + _options = new Options(); + Option aOption = new Option("a", "Aa", false, "option A"); + Option bOption = new Option("b", "Bb", false, "option B"); + OptionGroup group1 = new OptionGroup(); + group1.addOption(aOption); + group1.addOption(bOption); + _options.addOptionGroup(group1); + } + + /** + * the setUp above used to print [-a | -b] [-a] [-b] + */ + public void testOptionGroupDuplication() { + String help = unifyNewLines(getFormattedHelp()); + String expectedHelp = unifyNewLines(new String("usage: syntax [-a | -b]\n-a,--Aa option A\n-b,--Bb option B\n")); + assertEquals("expected usage to be '" + expectedHelp + "' instead of '" + help + "'", + expectedHelp, + help); + } + + /** + * Options following an option group used to be non blank separated: [-b | -a][-o] instead of + * [-b | -a] [-o] + */ + public void testOptionGroupSubsequentOptions() { + _options.addOption(new Option("o", "Option O")); + String help = getFormattedHelp(); + assertTrue(help.indexOf("][") < 0); + assertTrue(help.indexOf("[-a | -b] [-o]") >= 0); + } + + // + // private methods + // + private String getFormattedHelp() { + HelpFormatter formatter = new HelpFormatter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter pw = new PrintWriter(baos); + formatter.printHelp(pw, 60, "syntax", null, _options, 0, 1, null, true); + pw.flush(); + String usage = baos.toString(); + return usage; + } + + /** + * replace the windows specific \r\n line endings with java like \n line endings + * + * @param in + * The string to be transformed + * @return The string with unified line endings + */ + private String unifyNewLines(String in) { + char[] inChars = in.toCharArray(); + StringBuilder b = new StringBuilder(inChars.length); + for (int i = 0; i < inChars.length; i++) { + char current = inChars[i]; + if (current == '\r') { + if (i < inChars.length) { + char next = inChars[i + 1]; + if (next == '\n') { + i++; + current = next; + } + } + } + b.append(current); + } + return b.toString(); + } +} diff --git a/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java b/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java @@ -0,0 +1,160 @@ +package org.apache.commons.cli; + +import java.math.BigDecimal; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import junit.textui.TestRunner; + +public class OptionBuilderTest extends TestCase { + + public OptionBuilderTest( String name ) { + super( name ); + } + + public static Test suite() { + return new TestSuite( OptionBuilderTest.class ); + } + + public static void main( String args[] ) { + TestRunner.run( suite() ); + } + + public void testCompleteOption( ) { + OptionBuilder.withLongOpt( "simple option"); + OptionBuilder.hasArg( ); + OptionBuilder.isRequired( ); + OptionBuilder.hasArgs( ); + OptionBuilder.withType( new BigDecimal( "10" ) ); + OptionBuilder.withDescription( "this is a simple option" ); + Option simple = OptionBuilder.create( 's' ); + + assertEquals( "s", simple.getOpt() ); + assertEquals( "simple option", simple.getLongOpt() ); + assertEquals( "this is a simple option", simple.getDescription() ); + assertEquals( simple.getType().getClass(), BigDecimal.class ); + assertTrue( simple.hasArg() ); + assertTrue( simple.isRequired() ); + assertTrue( simple.hasArgs() ); + } + + public void testTwoCompleteOptions( ) { + OptionBuilder.withLongOpt( "simple option"); + OptionBuilder.hasArg( ); + OptionBuilder.isRequired( ); + OptionBuilder.hasArgs( ); + OptionBuilder.withType( new BigDecimal( "10" ) ); + OptionBuilder.withDescription( "this is a simple option" ); + Option simple = OptionBuilder.create( 's' ); + + assertEquals( "s", simple.getOpt() ); + assertEquals( "simple option", simple.getLongOpt() ); + assertEquals( "this is a simple option", simple.getDescription() ); + assertEquals( simple.getType().getClass(), BigDecimal.class ); + assertTrue( simple.hasArg() ); + assertTrue( simple.isRequired() ); + assertTrue( simple.hasArgs() ); + + OptionBuilder.withLongOpt( "dimple option"); + OptionBuilder.hasArg( ); + OptionBuilder.withDescription( "this is a dimple option" ); + simple = OptionBuilder.create( 'd' ); + + assertEquals( "d", simple.getOpt() ); + assertEquals( "dimple option", simple.getLongOpt() ); + assertEquals( "this is a dimple option", simple.getDescription() ); + assertNull( simple.getType() ); + assertTrue( simple.hasArg() ); + assertTrue( !simple.isRequired() ); + assertTrue( !simple.hasArgs() ); + } + + public void testBaseOptionCharOpt() { + OptionBuilder.withDescription( "option description"); + Option base = OptionBuilder.create( 'o' ); + + assertEquals( "o", base.getOpt() ); + assertEquals( "option description", base.getDescription() ); + assertTrue( !base.hasArg() ); + } + + public void testBaseOptionStringOpt() { + OptionBuilder.withDescription( "option description"); + Option base = OptionBuilder.create( "o" ); + + assertEquals( "o", base.getOpt() ); + assertEquals( "option description", base.getDescription() ); + assertTrue( !base.hasArg() ); + } + + public void testSpecialOptChars() { + + // '?' + try { + OptionBuilder.withDescription( "help options" ); + Option opt = OptionBuilder.create( '?' ); + assertEquals( "?", opt.getOpt() ); + } + catch( IllegalArgumentException arg ) { + fail( "IllegalArgumentException caught" ); + } + + // '@' + try { + OptionBuilder.withDescription( "read from stdin" ); + Option opt = OptionBuilder.create( '@' ); + assertEquals( "@", opt.getOpt() ); + } + catch( IllegalArgumentException arg ) { + fail( "IllegalArgumentException caught" ); + } + } + + public void testOptionArgNumbers() { + OptionBuilder.withDescription( "option description" ); + OptionBuilder.hasArgs( 2 ); + Option opt = OptionBuilder.create( 'o' ); + assertEquals( 2, opt.getArgs() ); + } + + public void testIllegalOptions() { + // bad single character option + try { + OptionBuilder.withDescription( "option description" ); + OptionBuilder.create( '"' ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // bad character in option string + try { + OptionBuilder.create( "opt`" ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // null option + try { + OptionBuilder.create( null ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // valid option + try { + OptionBuilder.create( "opt" ); + // success + } + catch( IllegalArgumentException exp ) { + fail( "IllegalArgumentException caught" ); + } + } +} \ No newline at end of file diff --git a/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java b/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java @@ -0,0 +1,43 @@ +package org.apache.commons.cli; + +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.TestCase; + +public class OptionGroupSortTest extends TestCase { + + private OptionGroup _optionGroup; + + protected void setUp() { + _optionGroup = new OptionGroup(); + _optionGroup.addOption(new Option("f", "first", false, "first")); + _optionGroup.addOption(new Option("s", "second", false, "second")); + _optionGroup.addOption(new Option("t", "third", false, "third")); + } + + public void testSortNames() { + Collection names = _optionGroup.getNames(); + Iterator namesIterator = names.iterator(); + assertTrue(namesIterator.hasNext()); + assertEquals("-f", (String) namesIterator.next()); + assertTrue(namesIterator.hasNext()); + assertEquals("-s", (String) namesIterator.next()); + assertTrue(namesIterator.hasNext()); + assertEquals("-t", (String) namesIterator.next()); + assertFalse(namesIterator.hasNext()); + } + + public void testSortOptions() { + Collection options = _optionGroup.getOptions(); + Iterator optionIterator = options.iterator(); + assertTrue(optionIterator.hasNext()); + assertEquals("first", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("second", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("third", ((Option) optionIterator.next()).getLongOpt()); + assertFalse(optionIterator.hasNext()); + } + +} diff --git a/installer/test/java/org/apache/commons/cli/OptionGroupTest.java b/installer/test/java/org/apache/commons/cli/OptionGroupTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionGroupTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: OptionGroupTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * @author John Keyes (john at integralsource.com) + * @version $Revision: 3134 $ + */ +public class OptionGroupTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser parser = new PosixParser(); + + + public static Test suite() + { + return new TestSuite ( OptionGroupTest.class ); + } + + public OptionGroupTest( String name ) + { + super( name ); + } + + public void setUp() + { + Option file = new Option( "f", "file", false, "file to process" ); + Option dir = new Option( "d", "directory", false, "directory to process" ); + OptionGroup group = new OptionGroup(); + group.addOption( file ); + group.addOption( dir ); + _options = new Options().addOptionGroup( group ); + + Option section = new Option( "s", "section", false, "section to process" ); + Option chapter = new Option( "c", "chapter", false, "chapter to process" ); + OptionGroup group2 = new OptionGroup(); + group2.addOption( section ); + group2.addOption( chapter ); + + _options.addOptionGroup( group2 ); + _options.addOption( "r", "revision", false, "revision number" ); + } + + public void tearDown() + { + } + + public void testSingleOptionFromGroup() + { + String[] args = new String[] { "-f" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleOption() + { + String[] args = new String[] { "-r" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is NOT set", !cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoValidOptions() + { + String[] args = new String[] { "-r", "-f" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleLongOption() + { + String[] args = new String[] { "--file" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoValidLongOptions() + { + String[] args = new String[] { "--revision", "--file" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testNoOptionsExtraArgs() + { + String[] args = new String[] { "arg1", "arg2" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is NOT set", !cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm TWO extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoOptionsFromGroup() + { + String[] args = new String[] { "-f", "-d" }; + + CommandLine cl = null; + try + { + cl = parser.parse( _options, args); + fail( "two arguments from group not allowed" ); + } + catch (ParseException e) + { + if( !( e instanceof AlreadySelectedException ) ) + { + fail( "incorrect exception caught:" + e.getMessage() + " in " + cl ); + } + } + } + + public void testTwoLongOptionsFromGroup() + { + String[] args = new String[] { "--file", "--directory" }; + + CommandLine cl = null; + try + { + cl = parser.parse( _options, args); + fail( "two arguments from group not allowed" ); + } + catch (ParseException e) + { + if( !( e instanceof AlreadySelectedException ) ) + { + fail( "incorrect exception caught:" + e.getMessage() + " in " + cl ); + } + } + } + + public void testTwoOptionsFromDifferentGroup() + { + String[] args = new String[] { "-f", "-s" }; + + try + { + CommandLine cl = parser.parse( _options, args); + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is set", cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm NO extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + +} diff --git a/installer/test/java/org/apache/commons/cli/OptionsTest.java b/installer/test/java/org/apache/commons/cli/OptionsTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionsTest.java @@ -0,0 +1,28 @@ +package org.apache.commons.cli; + +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.TestCase; + +public class OptionsTest extends TestCase { + + private Options _options; + + public void testSortAsAdded() { + _options = new Options(); + _options.setSortAsAdded(true); + _options.addOption("f", "first", false, "first"); + _options.addOption("s", "second", false, "second"); + _options.addOption("t", "third", false, "third"); + Collection optionCollection = _options.getOptions(); + Iterator optionIterator = optionCollection.iterator(); + assertTrue(optionIterator.hasNext()); + assertEquals("first", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("second", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("third", ((Option) optionIterator.next()).getLongOpt()); + assertFalse(optionIterator.hasNext()); + } +} diff --git a/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java b/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ParseRequiredTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * @author John Keyes (john at integralsource.com) + * @version $Revision: 3134 $ + */ +public class ParseRequiredTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser parser = new PosixParser(); + + public static Test suite() { + return new TestSuite(ParseRequiredTest.class); + } + + public ParseRequiredTest(String name) + { + super(name); + } + + public void setUp() + { + OptionBuilder.withLongOpt( "bfile" ); + OptionBuilder.hasArg(); + OptionBuilder.isRequired(); + OptionBuilder.withDescription( "set the value of [b]" ); + Option opt2 = OptionBuilder.create( 'b' ); + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption( opt2 ); + } + + public void tearDown() + { + + } + + public void testWithRequiredOption() + { + String[] args = new String[] { "-b", "file" }; + + try + { + CommandLine cl = parser.parse(_options,args); + + assertTrue( "Confirm -a is NOT set", !cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("file") ); + assertTrue( "Confirm NO of extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testOptionAndRequiredOption() + { + String[] args = new String[] { "-a", "-b", "file" }; + + try + { + CommandLine cl = parser.parse(_options,args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("file") ); + assertTrue( "Confirm NO of extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMissingRequiredOption() + { + String[] args = new String[] { "-a" }; + + CommandLine cl = null; + try + { + cl = parser.parse(_options,args); + fail( "exception should have been thrown" ); + } + catch (ParseException e) + { + if( !( e instanceof MissingOptionException ) ) + { + fail( "expected to catch MissingOptionException in " + cl ); + } + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/ParseTest.java b/installer/test/java/org/apache/commons/cli/ParseTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ParseTest.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ParseTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ParseTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser _parser = null; + + public static Test suite() { + return new TestSuite(ParseTest.class); + } + + public ParseTest(String name) + { + super(name); + } + + public void setUp() + { + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption("b", + "bfile", + true, + "set the value of [b]") + .addOption("c", + "copt", + false, + "turn [c] on or off"); + + _parser = new PosixParser(); + } + + public void tearDown() + { + + } + + public void testSimpleShort() + { + String[] args = new String[] { "-a", + "-b", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSimpleLong() + { + String[] args = new String[] { "--enable-a", + "--bfile", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testComplexShort() + { + String[] args = new String[] { "-acbtoast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testExtraOption() + { + String[] args = new String[] { "-adbtoast", + "foo", "bar" }; + + boolean caught = false; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 3); + } + catch (UnrecognizedOptionException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + assertTrue( "Confirm UnrecognizedOptionException caught", caught ); + } + + public void testMissingArg() + { + + String[] args = new String[] { "-acb" }; + + boolean caught = false; + + CommandLine cl = null; + try + { + cl = _parser.parse(_options, args); + } + catch (MissingArgumentException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + + assertTrue( "Confirm MissingArgumentException caught " + cl, caught ); + } + + public void testStop() + { + String[] args = new String[] { "-c", + "foober", + "-btoast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultiple() + { + String[] args = new String[] { "-c", + "foobar", + "-btoast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultipleWithLong() + { + String[] args = new String[] { "--copt", + "foobar", + "--bfile", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options,args, + true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testDoubleDash() + { + String[] args = new String[] { "--copt", + "--", + "-b", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm -b is not set", ! cl.hasOption("b") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleDash() + { + String[] args = new String[] { "--copt", + "-b", "-", + "-a", + "-" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("-") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("-") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + + } +} diff --git a/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java b/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: PatternOptionBuilderTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ +package org.apache.commons.cli; + +import java.math.BigDecimal; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test case for the PatternOptionBuilder class + * + * @author Henri Yandell + **/ +public class PatternOptionBuilderTest +extends TestCase +{ + public static void main( String[] args ) + { + String[] testName = { PatternOptionBuilderTest.class.getName() }; + junit.textui.TestRunner.main(testName); + } + + public static TestSuite suite() + { + return new TestSuite(PatternOptionBuilderTest.class); + } + + public PatternOptionBuilderTest( String s ) + { + super( s ); + } + + public void testSimplePattern() + { + try { + Options options = PatternOptionBuilder.parsePattern("a:b at cde>f+n%t/"); + String[] args = new String[] { "-c", "-a", "foo", "-b", "java.util.Vector", "-e", "build.xml", "-f", "java.util.Calendar", "-n", "4.5", "-t", "http://jakarta.apache.org/" }; + + CommandLineParser parser = new PosixParser(); + CommandLine line = parser.parse(options,args); + + // tests the char methods of CommandLine that delegate to + // the String methods + assertEquals("flag a", "foo", line.getOptionValue("a")); + assertEquals("flag a", "foo", line.getOptionValue('a')); + assertEquals("string flag a", "foo", line.getOptionObject("a")); + assertEquals("string flag a", "foo", line.getOptionObject('a')); + assertEquals("object flag b", new java.util.Vector(), line.getOptionObject("b")); + assertEquals("object flag b", new java.util.Vector(), line.getOptionObject('b')); + assertEquals("boolean true flag c", true, line.hasOption("c")); + assertEquals("boolean true flag c", true, line.hasOption('c')); + assertEquals("boolean false flag d", false, line.hasOption("d")); + assertEquals("boolean false flag d", false, line.hasOption('d')); + assertEquals("file flag e", new java.io.File("build.xml"), line.getOptionObject("e")); + assertEquals("file flag e", new java.io.File("build.xml"), line.getOptionObject('e')); + assertEquals("class flag f", java.util.Calendar.class, line.getOptionObject("f")); + assertEquals("class flag f", java.util.Calendar.class, line.getOptionObject('f')); + assertEquals("number flag n", new BigDecimal("4.5"), line.getOptionObject("n")); + assertEquals("number flag n", new BigDecimal("4.5"), line.getOptionObject('n')); + assertEquals("url flag t", new java.net.URL("http://jakarta.apache.org/"), line.getOptionObject("t")); + assertEquals("url flag t", new java.net.URL("http://jakarta.apache.org/"), line.getOptionObject('t')); + /// DATES NOT SUPPORTED YET. + // assertEquals("number flag t", new java.util.Date(1023400137276L), line.getOptionObject('z')); + // input is: "Thu Jun 06 17:48:57 EDT 2002" + } + catch( ParseException exp ) { + fail( exp.getMessage() ); + } + catch( java.net.MalformedURLException exp ) { + fail( exp.getMessage() ); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/PosixParserTest.java b/installer/test/java/org/apache/commons/cli/PosixParserTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/PosixParserTest.java @@ -0,0 +1,138 @@ +package org.apache.commons.cli; + +import junit.framework.TestCase; + +public class PosixParserTest extends TestCase { + + private static final String TEST_SHORT = "t"; + private static final String TEST_LONG = "test"; + private static final String TEST_DESC = "test option"; + + private static final String TEST_SHORT_OPTION = "-t"; + private static final String TEST_LONG_OPTION = "--test"; + + private static final String ARGUMENT = "argument"; + + private Options _options; + private Parser _parser; + + protected void setUp() { + _parser = new PosixParser(); + + _options = new Options(); + Option testOption = new Option(TEST_SHORT, TEST_LONG, false, TEST_DESC); + _options.addOption(testOption); + } + + /** + * test that an unknown single option and a double hyphen option (with or without argument) are treated the same + */ + public void testFlattenStop() { + boolean stopAtNonOption = true; // means unallowed tokens should not be added + String[] args; + String[] expectedFlattened; + + // unknown single dash option + args = new String[] { "-u" }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "-u", TEST_SHORT_OPTION }; + expectedFlattened = new String[] { TEST_SHORT_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option + args = new String[] { "--unknown" }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", TEST_LONG_OPTION }; + expectedFlattened = new String[] { TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after = + args = new String[] { "--unknown=" + ARGUMENT }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown="+ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after ' ' + args = new String[] { "--unknown", ARGUMENT }; + expectedFlattened = new String[] { ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + /** + * test that an unknown single option and a double hyphen option (with or without argument) are treated the same + */ + public void testFlattenNoStop() { + boolean stopAtNonOption = false; // means every token should be added + String[] args; + String[] expectedFlattened; + + // unknown single dash option + args = new String[] { "-u" }; + expectedFlattened = new String[] { "-u" }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "-u", TEST_SHORT_OPTION }; + expectedFlattened = new String[] { "-u", TEST_SHORT_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option + args = new String[] { "--unknown" }; + expectedFlattened = new String[] { "--unknown" }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after = + args = new String[] { "--unknown=" + ARGUMENT }; + expectedFlattened = new String[] { "--unknown", ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown="+ ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after ' ' + args = new String[] { "--unknown", ARGUMENT }; + expectedFlattened = new String[] { "--unknown", ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + /** + * test that a misspelled long option (-test instead of --test) is not interpreted as -t est + */ + public void testMisspelledLongOption() { + boolean stopAtNonOption = false; // means every token should be added + String[] args; + String[] expectedFlattened; + + // unknown single dash long option + String singleDashLongOption = "-" + TEST_LONG; + args = new String[] { singleDashLongOption }; + expectedFlattened = new String[] { singleDashLongOption }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + // + // private stuff + // + + /** + * Assert that the content of the specified object arrays is equal + */ + private void assertEquals(Object[] correct, Object[] tested) { + assertEquals("different array lengths:", correct.length, tested.length); + for (int i = 0; i < correct.length; i++) { + assertEquals("position " + i + " of array differs", correct[i], tested[i]); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java b/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: TestHelpFormatter.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ +package org.apache.commons.cli; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test case for the HelpFormatter class + * + * @author Slawek Zachcial + * @author John Keyes ( john at integralsource.com ) + **/ +public class TestHelpFormatter extends TestCase +{ + public static void main( String[] args ) + { + String[] testName = { TestHelpFormatter.class.getName() }; + junit.textui.TestRunner.main(testName); + } + + public static TestSuite suite() + { + return new TestSuite(TestHelpFormatter.class); + } + + public TestHelpFormatter( String s ) + { + super( s ); + } + + public void testFindWrapPos() + throws Exception + { + HelpFormatter hf = new HelpFormatter(); + + String text = "This is a test."; + //text width should be max 8; the wrap postition is 7 + assertEquals("wrap position", 7, hf.findWrapPos(text, 8, 0)); + //starting from 8 must give -1 - the wrap pos is after end + assertEquals("wrap position 2", -1, hf.findWrapPos(text, 8, 8)); + //if there is no a good position before width to make a wrapping look for the next one + text = "aaaa aa"; + assertEquals("wrap position 3", 4, hf.findWrapPos(text, 3, 0)); + } + + public void testPrintWrapped() + throws Exception + { + StringBuffer sb = new StringBuffer(); + HelpFormatter hf = new HelpFormatter(); + + String text = "This is a test."; + String expected; + + expected = "This is a" + hf.defaultNewLine + "test."; + hf.renderWrappedText(sb, 12, 0, text); + assertEquals("single line text", expected, sb.toString()); + + sb.setLength(0); + expected = "This is a" + hf.defaultNewLine + " test."; + hf.renderWrappedText(sb, 12, 4, text); + assertEquals("single line padded text", expected, sb.toString()); + + text = + "aaaa aaaa aaaa" + hf.defaultNewLine + + "aaaaaa" + hf.defaultNewLine + + "aaaaa"; + + expected = text; + sb.setLength(0); + hf.renderWrappedText(sb, 16, 0, text); + assertEquals("multi line text", expected, sb.toString()); + + expected = + "aaaa aaaa aaaa" + hf.defaultNewLine + + " aaaaaa" + hf.defaultNewLine + + " aaaaa"; + sb.setLength(0); + hf.renderWrappedText(sb, 16, 4, text); + assertEquals("multi-line padded text", expected, sb.toString()); + } + + public void testPrintOptions() + throws Exception + { + StringBuffer sb = new StringBuffer(); + HelpFormatter hf = new HelpFormatter(); + final int leftPad = 1; + final int descPad = 3; + final String lpad = hf.createPadding(leftPad); + final String dpad = hf.createPadding(descPad); + Options options = null; + String expected = null; + + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa"); + expected = lpad + "-a" + dpad + "aaaa aaaa aaaa aaaa aaaa"; + hf.renderOptions(sb, 60, options, leftPad, descPad); + assertEquals("simple non-wrapped option", expected, sb.toString()); + + int nextLineTabStop = leftPad+descPad+"-a".length(); + expected = + lpad + "-a" + dpad + "aaaa aaaa aaaa" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "aaaa aaaa"; + sb.setLength(0); + hf.renderOptions(sb, nextLineTabStop+17, options, leftPad, descPad); + assertEquals("simple wrapped option", expected, sb.toString()); + + + options = new Options().addOption("a", "aaa", false, "dddd dddd dddd dddd"); + expected = lpad + "-a,--aaa" + dpad + "dddd dddd dddd dddd"; + sb.setLength(0); + hf.renderOptions(sb, 60, options, leftPad, descPad); + assertEquals("long non-wrapped option", expected, sb.toString()); + + nextLineTabStop = leftPad+descPad+"-a,--aaa".length(); + expected = + lpad + "-a,--aaa" + dpad + "dddd dddd" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "dddd dddd"; + sb.setLength(0); + hf.renderOptions(sb, 25, options, leftPad, descPad); + assertEquals("long wrapped option", expected, sb.toString()); + + options = new Options(). + addOption("a", "aaa", false, "dddd dddd dddd dddd"). + addOption("b", false, "feeee eeee eeee eeee"); + expected = + lpad + "-a,--aaa" + dpad + "dddd dddd" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "dddd dddd" + hf.defaultNewLine + + lpad + "-b " + dpad + "feeee eeee" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "eeee eeee"; + sb.setLength(0); + hf.renderOptions(sb, 25, options, leftPad, descPad); + assertEquals("multiple wrapped options", expected, sb.toString()); + } + + public void testAutomaticUsage() + throws Exception + { + HelpFormatter hf = new HelpFormatter(); + Options options = null; + String expected = "usage: app [-a]"; + ByteArrayOutputStream out = new ByteArrayOutputStream( ); + PrintWriter pw = new PrintWriter( out ); + + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa"); + hf.printUsage( pw, 60, "app", options ); + pw.flush(); + assertEquals("simple auto usage", expected, out.toString().trim()); + out.reset(); + + expected = "usage: app [-b] [-a]"; + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa") + .addOption("b", false, "bbb" ); + hf.printUsage( pw, 60, "app", options ); + pw.flush(); + assertEquals("simple auto usage", expected, out.toString().trim()); + out.reset(); + } +} diff --git a/installer/test/java/org/apache/commons/cli/ValueTest.java b/installer/test/java/org/apache/commons/cli/ValueTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ValueTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ValueTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ValueTest extends TestCase +{ + + public static Test suite() { + return new TestSuite(ValueTest.class); + } + + private CommandLine _cl = null; + private Options opts = new Options(); + + public ValueTest(String name) + { + super(name); + } + + public void setUp() + { + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "set -b"); + + opts.addOption("c", + "c", + false, + "toggle -c"); + + opts.addOption("d", + "d", + true, + "set -d"); + + OptionBuilder.hasOptionalArg(); + opts.addOption( OptionBuilder.create( 'e') ); + + OptionBuilder.hasOptionalArg(); + OptionBuilder.withLongOpt( "fish" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs(); + OptionBuilder.withLongOpt( "gravy" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs( 2 ); + OptionBuilder.withLongOpt( "hide" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs( 2 ); + opts.addOption( OptionBuilder.create( 'i' ) ); + + OptionBuilder.hasOptionalArgs( ); + opts.addOption( OptionBuilder.create( 'j' ) ); + + String[] args = new String[] { "-a", + "-b", "foo", + "--c", + "--d", "bar" + }; + + try + { + CommandLineParser parser = new PosixParser(); + _cl = parser.parse(opts,args); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void tearDown() + { + + } + + public void testShortNoArg() + { + assertTrue( _cl.hasOption("a") ); + assertNull( _cl.getOptionValue("a") ); + } + + public void testShortWithArg() + { + assertTrue( _cl.hasOption("b") ); + assertNotNull( _cl.getOptionValue("b") ); + assertEquals( _cl.getOptionValue("b"), "foo"); + } + + public void testLongNoArg() + { + assertTrue( _cl.hasOption("c") ); + assertNull( _cl.getOptionValue("c") ); + } + + public void testLongWithArg() + { + assertTrue( _cl.hasOption("d") ); + assertNotNull( _cl.getOptionValue("d") ); + assertEquals( _cl.getOptionValue("d"), "bar"); + } + + public void testShortOptionalArgNoValue() + { + String[] args = new String[] { "-e" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("e") ); + assertNull( cmd.getOptionValue("e") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalArgValue() + { + String[] args = new String[] { "-e", "everything" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("e") ); + assertEquals( "everything", cmd.getOptionValue("e") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalNoValue() + { + String[] args = new String[] { "--fish" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("fish") ); + assertNull( cmd.getOptionValue("fish") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalArgValue() + { + String[] args = new String[] { "--fish", "face" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("fish") ); + assertEquals( "face", cmd.getOptionValue("fish") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalArgValues() + { + String[] args = new String[] { "-j", "ink", "idea" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("j") ); + assertEquals( "ink", cmd.getOptionValue("j") ); + assertEquals( "ink", cmd.getOptionValues("j")[0] ); + assertEquals( "idea", cmd.getOptionValues("j")[1] ); + assertEquals( cmd.getArgs().length, 0 ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalArgValues() + { + String[] args = new String[] { "--gravy", "gold", "garden" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("gravy") ); + assertEquals( "gold", cmd.getOptionValue("gravy") ); + assertEquals( "gold", cmd.getOptionValues("gravy")[0] ); + assertEquals( "garden", cmd.getOptionValues("gravy")[1] ); + assertEquals( cmd.getArgs().length, 0 ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalNArgValues() + { + String[] args = new String[] { "-i", "ink", "idea", "isotope", "ice" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("i") ); + assertEquals( "ink", cmd.getOptionValue("i") ); + assertEquals( "ink", cmd.getOptionValues("i")[0] ); + assertEquals( "idea", cmd.getOptionValues("i")[1] ); + assertEquals( cmd.getArgs().length, 2 ); + assertEquals( "isotope", cmd.getArgs()[0] ); + assertEquals( "ice", cmd.getArgs()[1] ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalNArgValues() + { + String[] args = new String[] { "--hide", "house", "hair", "head" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("hide") ); + assertEquals( "house", cmd.getOptionValue("hide") ); + assertEquals( "house", cmd.getOptionValues("hide")[0] ); + assertEquals( "hair", cmd.getOptionValues("hide")[1] ); + assertEquals( cmd.getArgs().length, 1 ); + assertEquals( "head", cmd.getArgs()[0] ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } +} diff --git a/installer/test/java/org/apache/commons/cli/ValuesTest.java b/installer/test/java/org/apache/commons/cli/ValuesTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ValuesTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ValuesTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import java.util.Arrays; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ValuesTest extends TestCase +{ + /** CommandLine instance */ + private CommandLine _cmdline = null; + private Option _option = null; + + public static Test suite() { + return new TestSuite( ValuesTest.class ); + } + + public ValuesTest( String name ) + { + super( name ); + } + + public void setUp() + { + Options opts = new Options(); + + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "set -b"); + + opts.addOption("c", + "c", + false, + "toggle -c"); + + opts.addOption("d", + "d", + true, + "set -d"); + + OptionBuilder.withLongOpt( "e" ); + OptionBuilder.hasArgs(); + OptionBuilder.withDescription( "set -e "); + opts.addOption( OptionBuilder.create( 'e' ) ); + + opts.addOption("f", + "f", + false, + "jk"); + + OptionBuilder.withLongOpt( "g" ); + OptionBuilder.hasArgs( 2 ); + OptionBuilder.withDescription( "set -g"); + opts.addOption( OptionBuilder.create( 'g' ) ); + + OptionBuilder.withLongOpt( "h" ); + OptionBuilder.hasArgs( 2 ); + OptionBuilder.withDescription( "set -h"); + opts.addOption( OptionBuilder.create( 'h' ) ); + + OptionBuilder.withLongOpt( "i" ); + OptionBuilder.withDescription( "set -i"); + opts.addOption( OptionBuilder.create( 'i' ) ); + + OptionBuilder.withLongOpt( "j" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -j"); + OptionBuilder.withValueSeparator( '=' ); + opts.addOption( OptionBuilder.create( 'j' ) ); + + OptionBuilder.withLongOpt( "k" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -k"); + OptionBuilder.withValueSeparator( '=' ); + opts.addOption( OptionBuilder.create( 'k' ) ); + + OptionBuilder.withLongOpt( "m" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -m"); + OptionBuilder.withValueSeparator( ); + _option = OptionBuilder.create( 'm' ); + + opts.addOption( _option ); + + String[] args = new String[] { "-a", + "-b", "foo", + "--c", + "--d", "bar", + "-e", "one", "two", + "-f", + "arg1", "arg2", + "-g", "val1", "val2" , "arg3", + "-h", "val1", "-i", + "-h", "val2", + "-jkey=value", + "-j", "key=value", + "-kkey1=value1", + "-kkey2=value2", + "-mkey=value"}; + + CommandLineParser parser = new PosixParser(); + + try + { + _cmdline = parser.parse(opts,args); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void tearDown() + { + + } + + public void testShortArgs() + { + assertTrue( _cmdline.hasOption("a") ); + assertTrue( _cmdline.hasOption("c") ); + + assertNull( _cmdline.getOptionValues("a") ); + assertNull( _cmdline.getOptionValues("c") ); + } + + public void testShortArgsWithValue() + { + assertTrue( _cmdline.hasOption("b") ); + assertTrue( _cmdline.getOptionValue("b").equals("foo")); + assertTrue( _cmdline.getOptionValues("b").length == 1); + + assertTrue( _cmdline.hasOption("d") ); + assertTrue( _cmdline.getOptionValue("d").equals("bar")); + assertTrue( _cmdline.getOptionValues("d").length == 1); + } + + public void testMultipleArgValues() + { + _cmdline.getOptionValues("e"); + String[] values = new String[] { "one", "two" }; + assertTrue( _cmdline.hasOption("e") ); + assertTrue( _cmdline.getOptionValues("e").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("e") ) ); + } + + public void testTwoArgValues() + { + _cmdline.getOptionValues("g"); + String[] values = new String[] { "val1", "val2" }; + assertTrue( _cmdline.hasOption("g") ); + assertTrue( _cmdline.getOptionValues("g").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("g") ) ); + } + + public void testComplexValues() + { + _cmdline.getOptionValues("h"); + String[] values = new String[] { "val1", "val2" }; + assertTrue( _cmdline.hasOption("i") ); + assertTrue( _cmdline.hasOption("h") ); + assertTrue( _cmdline.getOptionValues("h").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("h") ) ); + } + + public void testExtraArgs() + { + String[] args = new String[] { "arg1", "arg2", "arg3" }; + assertTrue( _cmdline.getArgs().length == 3 ); + assertTrue( Arrays.equals( args, _cmdline.getArgs() ) ); + } + + public void testCharSeparator() + { + // tests the char methods of CommandLine that delegate to + // the String methods + String[] values = new String[] { "key", "value", "key", "value" }; + assertTrue( _cmdline.hasOption( "j" ) ); + assertTrue( _cmdline.hasOption( 'j' ) ); + assertTrue( _cmdline.getOptionValues( "j" ).length == 4); + assertTrue( _cmdline.getOptionValues( 'j' ).length == 4); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "j" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'j' ) ) ); + + values = new String[] { "key1", "value1", "key2", "value2" }; + assertTrue( _cmdline.hasOption( "k" ) ); + assertTrue( _cmdline.hasOption( 'k' ) ); + assertTrue( _cmdline.getOptionValues( "k" ).length == 4 ); + assertTrue( _cmdline.getOptionValues( 'k' ).length == 4 ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "k" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'k' ) ) ); + + values = new String[] { "key", "value" }; + assertTrue( _cmdline.hasOption( "m" ) ); + assertTrue( _cmdline.hasOption( 'm' ) ); + assertTrue( _cmdline.getOptionValues( "m" ).length == 2); + assertTrue( _cmdline.getOptionValues( 'm' ).length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "m" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'm' ) ) ); + } + + /** + * jkeyes - commented out this test as the new architecture + * breaks this type of functionality. I have left the test + * here in case I get a brainwave on how to resolve this. + */ + /* + public void testGetValue() + { + // the 'm' option + assertTrue( _option.getValues().length == 2 ); + assertEquals( _option.getValue(), "key" ); + assertEquals( _option.getValue( 0 ), "key" ); + assertEquals( _option.getValue( 1 ), "value" ); + + try { + assertEquals( _option.getValue( 2 ), "key" ); + fail( "IndexOutOfBounds not caught" ); + } + catch( IndexOutOfBoundsException exp ) { + + } + + try { + assertEquals( _option.getValue( -1 ), "key" ); + fail( "IndexOutOfBounds not caught" ); + } + catch( IndexOutOfBoundsException exp ) { + + } + } + */ +} diff --git a/installer/test/java/org/python/util/install/ChildProcessExample.java b/installer/test/java/org/python/util/install/ChildProcessExample.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChildProcessExample.java @@ -0,0 +1,32 @@ + +package org.python.util.install; + +/** + * An example child process that generates some output. + */ +public class ChildProcessExample { + + public ChildProcessExample() { + System.out.println("[ChildProcessExample] is now here."); + } + + public static void main(String args[]) { + int i = 0; + new ChildProcessExample(); + for (i = 0; i < 10; i++) { + System.out.println("[ChildProcessExample] printing to stdout " + i); + // occasionally print to stderr, too + if (i % 3 == 0) { + System.err.println("[ChildProcessExample] printing to stderr " + i); + } + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + System.out.println("[ChildProcessExample] Exiting"); + System.exit(0); + } + +} \ No newline at end of file diff --git a/installer/test/java/org/python/util/install/ChildProcessTest.java b/installer/test/java/org/python/util/install/ChildProcessTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChildProcessTest.java @@ -0,0 +1,80 @@ +package org.python.util.install; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import junit.framework.TestCase; + +public class ChildProcessTest extends TestCase { + + private final static String CLASS_NAME = "org.python.util.install.ChildProcessExample"; + + /** + * test a default child process + */ + public void testDefault() { + ChildProcess childProcess = new ChildProcess(); + String command[] = buildJavaCommand(CLASS_NAME); + childProcess.setCommand(command); + childProcess.setDebug(true); + int exitValue = childProcess.run(); + assertEquals("Expected child process to end normally instead of " + exitValue, 0, exitValue); + } + + /** + * test the child process with a timeout + */ + public void testTimeout() { + ChildProcess childProcess = new ChildProcess(); + String command[] = buildJavaCommand(CLASS_NAME); + childProcess.setCommand(command); + childProcess.setDebug(true); + childProcess.setTimeout(2000); // timeout to 2 seconds + int exitValue = childProcess.run(); + assertEquals("Expected child process to be destroyed instead of " + exitValue, + ChildProcess.DESTROYED_AFTER_TIMEOUT, + exitValue); + } + + /** + * test silent mode + */ + public void testSilent() throws IOException { + ChildProcess childProcess = new ChildProcess(); + String command[] = new String[] {"lwiklsl", "-siwK"}; + childProcess.setCommand(command); + childProcess.setDebug(false); + childProcess.setSilent(true); + ByteArrayOutputStream redirectedErr = new ByteArrayOutputStream(); + ByteArrayOutputStream redirectedOut = new ByteArrayOutputStream(); + int exitValue = 0; + PrintStream oldErr = System.err; + PrintStream oldOut = System.out; + try { + System.setErr(new PrintStream(redirectedErr)); + System.setOut(new PrintStream(redirectedOut)); + exitValue = childProcess.run(); + } finally { + System.setErr(oldErr); + System.setOut(oldOut); + } + assertTrue(0 != exitValue); + redirectedErr.flush(); + redirectedOut.flush(); + assertEquals(0, redirectedErr.size()); + assertEquals(0, redirectedOut.size()); + } + + // + // private methods + // + private String[] buildJavaCommand(String classAndArguments) { + String quote = ""; + if (System.getProperty("os.name", "unknown").toLowerCase().indexOf("windows") >= 0) { + quote = "\""; + } + String classpath = System.getProperty("java.class.path"); + return new String[] {"java", "-classpath", quote.concat(classpath).concat(quote), classAndArguments}; + } +} \ No newline at end of file diff --git a/installer/test/java/org/python/util/install/ChmodTest_Standalone.java b/installer/test/java/org/python/util/install/ChmodTest_Standalone.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChmodTest_Standalone.java @@ -0,0 +1,57 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; + +/** + * Helper class to test of 'chmod' on different platforms + */ +public class ChmodTest_Standalone { + + private static String _mode = "755"; // default mode + + public static void main(String[] args) { + // get mode from first argument, if present + if (args.length > 0) { + _mode = args[0]; + } + + // create an empty test file in the current directory + String curdir = System.getProperty("user.dir"); + File testFile = new File(curdir, "chmod.test"); + String path = testFile.getAbsolutePath(); + if (!testFile.exists()) { + try { + if (!testFile.createNewFile()) { + System.err.println(getPrefix() + "unable to create file " + path); + System.exit(1); + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + + // apply the chmod command on the test file + if (!testFile.exists()) { + System.err.println(getPrefix() + "unable to create file " + path); + System.exit(1); + } else { + String command[] = new String[] {"chmod", _mode, path}; + ChildProcess childProcess = new ChildProcess(command, 3000); + childProcess.setDebug(true); + int exitValue = childProcess.run(); + if (exitValue != 0) { + System.err.println(getPrefix() + "error during chmod"); + } else { + System.out.println(getPrefix() + "chmod command executed on " + path); + } + System.exit(exitValue); + } + } + + private static String getPrefix() { + return "[ChmodTest_Standalone] "; + } + +} diff --git a/installer/test/java/org/python/util/install/FileHelperTest.java b/installer/test/java/org/python/util/install/FileHelperTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/FileHelperTest.java @@ -0,0 +1,271 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; + +import junit.framework.TestCase; +import junit.runner.TestSuiteLoader; + +import org.python.util.install.driver.Autotest; + +public class FileHelperTest extends TestCase { + + private static final String JYTHON_TEST_TEMPLATE = "jython_test.template"; + + private static final String LOGO_GIF = "logo.gif"; + + private static final String JYTHON_SMALL_C_PNG = "jython_small_c.png"; + + public void testCreateTempDirectory_WasFile() throws IOException { + File file = File.createTempFile("some_prefix", ""); + assertTrue(file.exists()); + assertTrue(file.isFile()); + assertTrue(FileHelper.createTempDirectory(file)); + assertTrue(file.exists()); + assertTrue(file.isDirectory()); + } + + public void testCreateTempDirectory_AlreadyPresent() throws IOException { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + assertTrue(FileHelper.createTempDirectory(dir)); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + } + + public void testCreateTempDirectory() throws IOException { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + File tmpDir = new File(dir, "tmp"); + assertFalse(tmpDir.exists()); + try { + assertTrue(FileHelper.createTempDirectory(tmpDir)); + assertTrue(tmpDir.exists()); + assertTrue(dir.isDirectory()); + } finally { + if (tmpDir.exists()) { + assertTrue(tmpDir.delete()); + } + } + } + + public void testCreateTempDirectory_Failure() throws Exception { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + File tmpFile = new File(dir, "tmpFailure"); + assertFalse(tmpFile.exists()); + try { + tmpFile.createNewFile(); + assertTrue(tmpFile.exists()); + assertTrue(tmpFile.isFile()); + Lock lock = null; + try { + lock = new Lock(tmpFile); + boolean created = FileHelper.createTempDirectory(tmpFile); + if (Installation.isWindows()) { + // locking currently only effective on windows + assertFalse(created); + } else { + // change if there is a locking mechanism + assertTrue(created); + } + } finally { + if (lock != null) { + lock.release(); + } + } + } finally { + if (tmpFile.exists()) { + assertTrue(tmpFile.delete()); + } + } + } + + public void testRmdir() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + assertTrue(FileHelper.rmdir(dir)); + } + + public void testRmdir_Failure() throws Exception { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + Lock lock = null; + try { + lock = new Lock(jython_bat); + boolean removed = FileHelper.rmdir(dir); + if (Installation.isWindows()) { + // locking currently only effective on windows + assertFalse(removed); + } else { + // change if there is a locking mechanism + assertTrue(removed); + } + } finally { + if (lock != null) { + lock.release(); + } + assertTrue(FileHelper.rmdir(dir)); + } + } + + public void testReadAll() throws Exception { + File file = File.createTempFile("testReadAll", ""); + final String contents = new String("line1 \n line2 \n"); + FileHelper.write(file, contents); + String readContents = FileHelper.readAll(file); + assertEquals(contents, readContents); + } + + public void testReadAll_InputStream() throws Exception { + URL url = FileHelper.getRelativeURL(Autotest.class, JYTHON_TEST_TEMPLATE); + assertNotNull(url); + URI uri = new URI(url.toString()); + File file = new File(uri); + assertNotNull(file); + assertTrue(file.exists()); + String expectedContents = FileHelper.readAll(file); + InputStream is = FileHelper.getRelativeURLAsStream(Autotest.class, JYTHON_TEST_TEMPLATE); + assertNotNull(is); + String contents = FileHelper.readAll(is); + assertEquals(expectedContents, contents); + // now from a .jar + is = FileHelper.getRelativeURLAsStream(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(is); + contents = FileHelper.readAll(is); + assertNotNull(contents); + assertEquals(964, contents.length()); + assertTrue(contents.startsWith("GIF89a&")); + } + + public void testReadAll_NonExisting() { + String readContents = null; + try { + readContents = FileHelper.readAll(new File("_non_existing")); + fail("FileNotFoundException expected"); + } catch (IOException e) { + assertNull(readContents); + } + } + + public void testGetRelativeURL() { + URL url = FileHelper.getRelativeURL(Installation.class, JYTHON_SMALL_C_PNG); + assertNotNull(url); + assertTrue(url.getPath().endsWith("org/python/util/install/".concat(JYTHON_SMALL_C_PNG))); + // now from a .jar + url = FileHelper.getRelativeURL(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(url); + assertTrue(url.getPath().endsWith("!/junit/runner/".concat(LOGO_GIF))); + } + + public void testGetRelativeURLAsStream() throws IOException { + InputStream is = FileHelper.getRelativeURLAsStream(Installation.class, JYTHON_SMALL_C_PNG); + assertNotNull(is); + try { + assertTrue(is.read() >= 0); + } finally { + is.close(); + } + // now from a .jar + is = FileHelper.getRelativeURLAsStream(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(is); + try { + assertTrue(is.read() >= 0); + } finally { + is.close(); + } + } + + public void testWrite() throws IOException { + File file = new File("testWrite"); + assertFalse(file.exists()); + try { + final String contents = new String("line1 \n line2 \n"); + FileHelper.write(file, contents); + assertTrue(file.exists()); + String readContents = FileHelper.readAll(file); + assertEquals(contents, readContents); + } finally { + if (file.exists()) { + assertTrue(file.delete()); + } + } + } + + public void testWrite_Existing() throws IOException { + File file = File.createTempFile("testWrite", ""); + assertTrue(file.exists()); + final String firstContents = "first dummy contents"; + FileHelper.write(file, firstContents); + assertEquals(firstContents, FileHelper.readAll(file)); + final String realContents = new String("line1 \n line2 \n"); + FileHelper.write(file, realContents); + assertEquals(realContents, FileHelper.readAll(file)); + } + + /** + * A poor man's file lock (does work on windows only) + */ + public static class Lock { + + private final FileOutputStream _fos; + + /** + * acquire a file lock + * + * @param file + * The file to be locked + * @throws FileNotFoundException + */ + public Lock(File file) throws FileNotFoundException { + _fos = new FileOutputStream(file); + } + + /** + * release the file lock + * + * @throws IOException + */ + public void release() throws IOException { + if (_fos != null) { + _fos.close(); + } + } + } +} diff --git a/installer/test/java/org/python/util/install/FrameInstallerTest.java b/installer/test/java/org/python/util/install/FrameInstallerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/FrameInstallerTest.java @@ -0,0 +1,88 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +import junit.framework.TestCase; + +public class FrameInstallerTest extends TestCase { + + public void testInitDefaultJava() { + FrameInstaller.initDefaultJava(); + JavaVersionInfo vInfo = FrameInstaller.getJavaVersionInfo(); + assertNotNull(vInfo); + String version = vInfo.getVersion(); + assertNotNull(version); + assertTrue(version.length() > 0); + String specificationVersion = vInfo.getSpecificationVersion(); + assertNotNull(specificationVersion); + assertTrue(specificationVersion.length() > 0); + String vendor = vInfo.getVendor(); + assertNotNull(vendor); + assertTrue(vendor.length() > 0); + } + + public void testJavaVersionInfo() { + String version = "1;2;3"; + String vendor = "jython [macrosystems]"; + String specificationVersion = "@spec 1,4"; + + JavaVersionInfo vInfo = new JavaVersionInfo(); + vInfo.setVersion(version); + vInfo.setVendor(vendor); + vInfo.setSpecificationVersion(specificationVersion); + + FrameInstaller.setJavaVersionInfo(vInfo); + JavaVersionInfo returnedInfo = FrameInstaller.getJavaVersionInfo(); + + assertNotNull(returnedInfo); + assertEquals(version, returnedInfo.getVersion()); + assertEquals(vendor, returnedInfo.getVendor()); + assertEquals(specificationVersion, returnedInfo.getSpecificationVersion()); + } + + public void testInstallationType() { + InstallationType installationType = new InstallationType(); + installationType.addLibraryModules(); + installationType.removeDemosAndExamples(); + installationType.removeDocumentation(); + installationType.addSources(); + + FrameInstaller.setInstallationType(installationType); + InstallationType returnedType = FrameInstaller.getInstallationType(); + + assertNotNull(returnedType); + assertTrue(returnedType.installLibraryModules()); + assertFalse(returnedType.installDemosAndExamples()); + assertFalse(returnedType.installDocumentation()); + assertTrue(returnedType.installSources()); + } + + public void testStandalone() { + InstallationType installationType = new InstallationType(); + installationType.setStandalone(); + assertTrue(installationType.installLibraryModules()); + assertFalse(installationType.installDemosAndExamples()); + assertFalse(installationType.installDocumentation()); + assertFalse(installationType.installSources()); + + FrameInstaller.setInstallationType(installationType); + InstallationType returnedType = FrameInstaller.getInstallationType(); + + assertNotNull(returnedType); + assertTrue(returnedType.isStandalone()); + assertTrue(returnedType.installLibraryModules()); + assertFalse(returnedType.installDemosAndExamples()); + assertFalse(returnedType.installDocumentation()); + assertFalse(returnedType.installSources()); + } + + public void testSetGetJavaHomeHandler() { + assertNotNull(FrameInstaller.getJavaHomeHandler()); + JavaHomeHandler handler1 = new JavaHomeHandler(); + JavaHomeHandler handler2 = new JavaHomeHandler("some/dir"); + FrameInstaller.setJavaHomeHandler(handler1); + assertEquals(handler1, FrameInstaller.getJavaHomeHandler()); + FrameInstaller.setJavaHomeHandler(handler2); + assertEquals(handler2, FrameInstaller.getJavaHomeHandler()); + } +} diff --git a/installer/test/java/org/python/util/install/InstallationTest.java b/installer/test/java/org/python/util/install/InstallationTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallationTest.java @@ -0,0 +1,107 @@ +package org.python.util.install; + +import java.io.File; + +import org.python.util.install.Installation.JavaVersionInfo; + +import junit.framework.TestCase; + +public class InstallationTest extends TestCase { + + public void testGetExternalJavaVersion() { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.NORMAL_RETURN, versionInfo.getErrorCode()); + assertEquals("", versionInfo.getReason()); + assertTrue(versionInfo.getVersion().length() > 0); + assertTrue(versionInfo.getSpecificationVersion().length() > 0); + assertTrue(versionInfo.getVersion().startsWith(versionInfo.getSpecificationVersion())); + assertNotNull(versionInfo.getVendor()); + assertNotSame("", versionInfo.getVendor()); + } + + public void testGetExternalJavaVersionWithError() { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler("non_existing/home"); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + String reason = versionInfo.getReason(); + assertTrue(reason.indexOf("invalid") >= 0); + } + + public void testGetExternalJavaVersionNoBinDirectory() { + File wrongHome = new File(System.getProperty("user.home")); + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(wrongHome.getAbsolutePath()); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + String reason = versionInfo.getReason(); + assertTrue(reason.indexOf("invalid") >= 0); + } + + public void testGetExternalJavaVersionNoJavaInBinDirectory() { + File wrongHome = new File(System.getProperty("user.home")); + File binDir = new File(wrongHome, "bin"); + assertFalse(binDir.exists()); + try { + assertTrue(binDir.mkdirs()); + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(wrongHome.getAbsolutePath()); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + assertTrue(versionInfo.getReason().indexOf("invalid") >= 0); + } finally { + if (binDir.exists()) { + binDir.delete(); + } + } + } + + public void testIsValidJavaVersion() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + + javaVersionInfo.setSpecificationVersion("1.1.9"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.2"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.3"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.4"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.5"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.6"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.7"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + } + + public void testGetJavaSpecificationVersion() { + String specificationVersion = "1.4.2"; + assertEquals(14, Installation.getJavaSpecificationVersion(specificationVersion)); + specificationVersion = "1.5.0"; + assertEquals(15, Installation.getJavaSpecificationVersion(specificationVersion)); + specificationVersion = "1.6.0"; + assertEquals(16, Installation.getJavaSpecificationVersion(specificationVersion)); + } + + public void testIsGNUJava() { + assertFalse(Installation.isGNUJava()); + String originalVmName = System.getProperty(Installation.JAVA_VM_NAME); + try { + // fake GNU java + System.setProperty(Installation.JAVA_VM_NAME, "GNU libgcj"); + assertTrue(Installation.isGNUJava()); + } finally { + System.setProperty(Installation.JAVA_VM_NAME, originalVmName); + assertFalse(Installation.isGNUJava()); + } + } + + public void testGetDefaultJavaVersion() { + JavaVersionInfo info = Installation.getDefaultJavaVersion(); + assertNotNull(info); + assertEquals(Installation.NORMAL_RETURN, info.getErrorCode()); + String specVersion = info.getSpecificationVersion(); + assertNotNull(specVersion); + assertTrue(specVersion.length() >= 3); + } + +} diff --git a/installer/test/java/org/python/util/install/InstallationTypeTest.java b/installer/test/java/org/python/util/install/InstallationTypeTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallationTypeTest.java @@ -0,0 +1,119 @@ +package org.python.util.install; + +import junit.framework.TestCase; + +// test checkin +public class InstallationTypeTest extends TestCase { + + private InstallationType _type; + + protected void setUp() { + _type = new InstallationType(); + } + + public void testConstruction() { + assertTrue(_type.isStandard()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testStandard() { + _type.setStandard(); + assertTrue(_type.isStandard()); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testMinimum() { + assertFalse(_type.isMinimum()); + _type.setMinimum(); + assertTrue(_type.isMinimum()); + assertFalse(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testAll() { + assertFalse(_type.isAll()); + _type.setAll(); + assertTrue(_type.isAll()); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testStandalone() { + assertFalse(_type.isStandalone()); + _type.setStandalone(); + assertTrue(_type.isStandalone()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertTrue(_type.isPredefined()); + + // sure to handle this as follows? + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + } + + public void testAddRemove() { + _type.removeDocumentation(); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.removeDemosAndExamples(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.addSources(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.addDocumentation(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + } + +} diff --git a/installer/test/java/org/python/util/install/InstallerCommandLineTest.java b/installer/test/java/org/python/util/install/InstallerCommandLineTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallerCommandLineTest.java @@ -0,0 +1,637 @@ +package org.python.util.install; + +import java.io.File; + +import junit.framework.TestCase; + +public class InstallerCommandLineTest extends TestCase { + + public void testValidArguments() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--directory", "c:/temp" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--type", "all" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-t", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type", "standard" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--verbose" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-A" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--autotest" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + } + + public void testInvalidArguments() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "--one" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--one argOne" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--one", "--two", "--three" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-o" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type", "weird" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testMissingArgument() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "--directory" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--type" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testUnknownArgument() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "yeah" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "yeah" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "yeah", "yoyo" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--type", "takatuka" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testOptionGroups() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-s", "-c" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-s", "-A" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-c", "-A" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "--console" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "--autotest" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--console", "--autotest" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-?", "-h" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-?", "--help" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testSilent() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-s" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); // expect required directory in silent mode + + args = new String[] { "-s", "-d", "/tmp" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + } + + public void testConsole() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); + } + + public void testGui() { + String[] args; + InstallerCommandLine commandLine; + + // normal gui startup without any arguments + assertTrue(Installation.isGuiAllowed()); + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasConsoleOption()); + assertFalse(commandLine.hasSilentOption()); + } + + /** + * simulate startup on a headless system (auto-switch to console mode) + */ + public void testHeadless() { + String[] args; + InstallerCommandLine commandLine; + boolean originalHeadless = Boolean.getBoolean(Installation.HEADLESS_PROPERTY_NAME); + try { + if (!originalHeadless) { + System.setProperty(Installation.HEADLESS_PROPERTY_NAME, "true"); + } + assertFalse(Installation.isGuiAllowed()); + + // without any arguments + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + + // with one argument + args = new String[] {"-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + + // with more arguments + args = new String[] {"-v", "-t", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + assertTrue(commandLine.hasTypeOption()); + InstallationType type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isMinimum()); + + // silent should override! + args = new String[] {"-v", "-s", "-d", "some_dir"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + File dir = commandLine.getTargetDirectory(); + assertNotNull(dir); + assertEquals("some_dir", dir.getName()); + + // -A (autotest) should override as well + args = new String[] {"-A"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertFalse(commandLine.hasSilentOption()); + + // console aready present should be no problem + args = new String[] {"-c", "-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); + assertFalse(commandLine.hasSilentOption()); + } finally { + if (!originalHeadless) { + System.setProperty(Installation.HEADLESS_PROPERTY_NAME, "false"); + assertTrue(Installation.isGuiAllowed()); + } + } + } + + public void testGNUSwitchToConsole() { + String originalVmName = System.getProperty(Installation.JAVA_VM_NAME); + try { + // fake GNU java + System.setProperty(Installation.JAVA_VM_NAME, "GNU libgcj"); + assertTrue(Installation.isGNUJava()); + String[] args; + InstallerCommandLine commandLine; + // expect auto switch + args = new String[] {"-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + // expect no auto switch + args = new String[] {"-s", "-d", "some_dir"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertFalse(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertTrue(commandLine.hasDirectoryOption()); + File dir = commandLine.getTargetDirectory(); + assertNotNull(dir); + assertEquals("some_dir", dir.getName()); + } finally { + System.setProperty(Installation.JAVA_VM_NAME, originalVmName); + assertFalse(Installation.isGNUJava()); + } + } + + + public void testDirectory() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-d", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + + args = new String[] { "-s", "--directory", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + } + + public void testType() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "all" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isAll()); + + args = new String[] { "--type", "standard" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isStandard()); + + args = new String[] { "--type", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isMinimum()); + + args = new String[] { "--type", "standalone" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isStandalone()); + + assertFalse(commandLine.getJavaHomeHandler().isDeviation()); + } + + public void testInclude() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "minimum", "-i", "mod" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "-i", "mod", "demo" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "-i", "mod", "demo", "doc" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "--include", "mod", "demo", "doc", "src" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-i", "modulo" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testExclude() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "all", "-e", "mod" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "-e", "mod", "demo" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "-e", "mod", "demo", "doc" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "--exclude", "mod", "demo", "doc", "src" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "--exclude", "sources" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testJavaHomeHandler() { + String[] args; + InstallerCommandLine commandLine; + final String javaHomeName = "java/home/dir"; + + args = new String[] { "-j", javaHomeName }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasJavaHomeOption()); + JavaHomeHandler javaHomeHandler = commandLine.getJavaHomeHandler(); + assertNotNull(javaHomeHandler); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + + args = new String[] { "--jre", javaHomeName }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasJavaHomeOption()); + javaHomeHandler = commandLine.getJavaHomeHandler(); + assertNotNull(javaHomeHandler); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + + assertNull(commandLine.getTargetDirectory()); + } + + public void testExamples() { + String[] args; + InstallerCommandLine commandLine; + final String javaHomeName = "java/home/dir"; + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); + + args = new String[] { "-s", "-d", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + + args = new String[] { "-s", "-d", "dir", "-t", "standard", "-j", javaHomeName, "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + assertTrue(commandLine.hasTypeOption()); + assertNotNull(commandLine.getInstallationType()); + assertTrue(commandLine.getInstallationType().installDemosAndExamples()); + assertTrue(commandLine.getInstallationType().installDocumentation()); + assertTrue(commandLine.getInstallationType().installLibraryModules()); + assertFalse(commandLine.getInstallationType().installSources()); + assertTrue(commandLine.hasJavaHomeOption()); + JavaHomeHandler javaHomeHandler = commandLine.getJavaHomeHandler(); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + assertTrue(commandLine.hasVerboseOption()); + + args = new String[] { "-s", "-d", "dir", "-t", "standard", "-e", "doc", "demo", "-i", "src", "-j", javaHomeName, "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + assertTrue(commandLine.hasTypeOption()); + assertNotNull(commandLine.getInstallationType()); + assertFalse(commandLine.getInstallationType().installDemosAndExamples()); + assertFalse(commandLine.getInstallationType().installDocumentation()); + assertTrue(commandLine.getInstallationType().installLibraryModules()); + assertTrue(commandLine.getInstallationType().installSources()); + assertTrue(commandLine.hasJavaHomeOption()); + javaHomeHandler = commandLine.getJavaHomeHandler(); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + assertTrue(commandLine.hasVerboseOption()); + } + + public void testHelp() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasHelpOption()); + + args = new String[] { "--help" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + args = new String[] { "-h" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + args = new String[] { "-?" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + // now print the help + commandLine.printHelp(); + } + + public void testHasVerboseOptionInArgs() { + String[] args = new String[0]; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "b", "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", InstallerCommandLine.VERBOSE_SHORT, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "-" + InstallerCommandLine.VERBOSE_SHORT, "c"}; + assertTrue(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", InstallerCommandLine.VERBOSE_LONG, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "-" + InstallerCommandLine.VERBOSE_LONG, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "--" + InstallerCommandLine.VERBOSE_LONG, "c"}; + assertTrue(InstallerCommandLine.hasVerboseOptionInArgs(args)); + } + +} diff --git a/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java b/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java @@ -0,0 +1,114 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; + +import junit.framework.TestCase; + +public class JavaHomeHandlerTest extends TestCase { + + private static final String JAVA_HOME = JavaHomeHandler.JAVA_HOME; + + private static final String JAVA = "java"; + + private static final String SOME_WEIRD_HOME = "some/weird/home"; + + private String _originalJavaHome; + + @Override + protected void setUp() throws Exception { + JavaHomeHandler.reset(); + _originalJavaHome = System.getProperty(JAVA_HOME); + } + + @Override + protected void tearDown() throws Exception { + System.setProperty(JAVA_HOME, _originalJavaHome); + } + + public void testGetExecutableName() throws IOException { + String executable = new JavaHomeHandler().getExecutableName(); + assertNotNull(executable); + assertTrue(executable.length() > JAVA.length()); + String homePath = createTempHome().getAbsolutePath(); + executable = new JavaHomeHandler(homePath).getExecutableName(); + assertTrue(executable.length() > JAVA.length()); + assertTrue(executable.indexOf(homePath) >= 0); + System.setProperty(JAVA_HOME, homePath); + executable = new JavaHomeHandler().getExecutableName(); + assertTrue(executable.length() > JAVA.length()); + assertTrue(executable.indexOf(homePath) >= 0); + } + + public void testGetExecutableName_NonExisting() { + String executable = new JavaHomeHandler(SOME_WEIRD_HOME).getExecutableName(); + assertEquals(JAVA, executable); // fallback + System.setProperty(JAVA_HOME, SOME_WEIRD_HOME); + executable = new JavaHomeHandler().getExecutableName(); + assertEquals(JAVA, executable); // fallback + } + + public void testCreateJavaHomeHandler() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(); + assertNotNull(handler); + System.setProperty(JAVA_HOME, SOME_WEIRD_HOME); + handler = new JavaHomeHandler(); + assertNotNull(handler); + System.setProperty(JAVA_HOME, createTempHome().getAbsolutePath()); + handler = new JavaHomeHandler(); + assertNotNull(handler); + } + + public void testCreateHandler_Deviation() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(SOME_WEIRD_HOME); + assertNotNull(handler); + handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertNotNull(handler); + } + + public void testIsDeviation() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertTrue(handler.isDeviation()); + handler = new JavaHomeHandler(); + assertFalse(handler.isDeviation()); + handler = new JavaHomeHandler(System.getProperty(JAVA_HOME)); + assertFalse(handler.isDeviation()); + } + + public void testGetJavaHome() throws IOException { + String tempHome = createTempHome().getAbsolutePath(); + JavaHomeHandler handler = new JavaHomeHandler(tempHome); + String home = handler.getHome().getAbsolutePath(); + assertEquals(tempHome, home); + try { + handler = new JavaHomeHandler(SOME_WEIRD_HOME); + } catch (InstallerException ie) { + assertEquals("no valid java home", ie.getMessage()); + } + } + + public void testIsValidJavaHome() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(SOME_WEIRD_HOME); + assertFalse(handler.isValidHome()); + handler = new JavaHomeHandler(); + assertTrue(handler.isValidHome()); + handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertTrue(handler.isValidHome()); + } + + private File createTempHome() throws IOException { + File home = File.createTempFile("JavaHomeHandler", "Test"); + assertTrue(FileHelper.createTempDirectory(home)); + File binDir = new File(home, "bin"); + assertTrue(binDir.mkdirs()); + String executableName = JAVA; + if (Installation.isWindows()) { + executableName = executableName.concat(".exe"); + } + File java = new File(binDir, executableName); + FileHelper.write(java, "dummy"); + assertTrue(java.exists()); + assertTrue(java.isFile()); + return home; + } +} diff --git a/installer/test/java/org/python/util/install/JavaTest_Standalone.java b/installer/test/java/org/python/util/install/JavaTest_Standalone.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/JavaTest_Standalone.java @@ -0,0 +1,32 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +/** + * Helper class to test an external java version + */ +public class JavaTest_Standalone { + + public static void main(String[] args) { + if (args.length > 0) { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(args[0]); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (versionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + System.err.println(versionInfo.getReason()); + } else { + System.out.println(getPrefix() + "java version:" + versionInfo.getVersion()); + System.out.println(getPrefix() + "java spec version:" + versionInfo.getSpecificationVersion()); + } + System.exit(versionInfo.getErrorCode()); + } else { + System.err.println(getPrefix() + "missing argument: please specify the java home directory " + + "(/bin directory assumed below)"); + System.exit(1); + } + } + + private static String getPrefix() { + return "[JavaTest_Standalone] "; + } + +} diff --git a/installer/test/java/org/python/util/install/StandalonePackagerTest.java b/installer/test/java/org/python/util/install/StandalonePackagerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/StandalonePackagerTest.java @@ -0,0 +1,183 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import junit.framework.TestCase; + +public class StandalonePackagerTest extends TestCase { + private File _sourceJarFile; + private File _targetJarFile; + + private File _contentFile; + private File _nextContentFile; + private File _additionalFile; + + private File _contentDir; + private File _additionalDir; + + private File _jarDir; + + protected void setUp() throws Exception { + _jarDir = File.createTempFile("jarDir", ""); + assertTrue(FileHelper.createTempDirectory(_jarDir)); + _targetJarFile = new File(_jarDir, "target.jar"); + _sourceJarFile = new File(_jarDir, "source.jar"); + + _contentDir = File.createTempFile("content", ""); + assertTrue(FileHelper.createTempDirectory(_contentDir)); + _contentFile = new File(_contentDir, "content.file"); + _contentFile.createNewFile(); + assertTrue(_contentFile.exists()); + + createSourceJar(); + } + + protected void tearDown() throws Exception { + if (_sourceJarFile != null) { + _sourceJarFile.delete(); + } + if (_targetJarFile != null) { + _targetJarFile.delete(); + } + if (_contentFile != null) { + _contentFile.delete(); + } + if (_nextContentFile != null) { + _nextContentFile.delete(); + } + if (_additionalFile != null) { + _additionalFile.delete(); + } + if (_contentDir != null) { + _contentDir.delete(); + } + if (_additionalDir != null) { + _additionalDir.delete(); + } + if (_jarDir != null) { + _jarDir.delete(); + } + } + + /** + * test the static method emptyDir() + */ + public void testEmptyDir() throws Exception { + File tempContentFile = new File(_contentDir, "temp"); + tempContentFile.createNewFile(); + assertTrue(tempContentFile.exists()); + File tempDir = new File(_contentDir, "tempDir"); + assertTrue(FileHelper.createTempDirectory(tempDir)); + + StandalonePackager.emptyDirectory(_contentDir, _contentFile); + assertTrue(_contentFile.exists()); + assertFalse(tempContentFile.exists()); + assertFalse(tempDir.exists()); + assertEquals(1, _contentDir.list().length); + } + + /** + * test adding a jar file, a directory, and another single file + */ + public void testAdd_Jar_Directory_File() throws IOException { + createAdditionalDirectory(); + _nextContentFile = File.createTempFile("nextContent.file", ""); + _nextContentFile.createNewFile(); + assertTrue(_nextContentFile.exists()); + + StandalonePackager packager = new StandalonePackager(_targetJarFile); + try { + packager.addJarFile(_sourceJarFile); + packager.addFullDirectory(_additionalDir); + packager.addFile(_nextContentFile, null); + } finally { + packager.close(); + } + + assertTrue(_targetJarFile.exists()); + + Map mandatoryEntries = new HashMap(8); + mandatoryEntries.put(_contentDir.getName(), "dir"); + mandatoryEntries.put(_contentFile.getName(), "file"); + mandatoryEntries.put(_nextContentFile.getName(), "file"); + mandatoryEntries.put(_additionalDir.getName(), "dir"); + mandatoryEntries.put(_additionalFile.getName(), "file"); + mandatoryEntries.put("META-INF", "dir"); + mandatoryEntries.put("MANIFEST.MF", "file"); + + JarFile targetJarFile = new JarFile(_targetJarFile); + try { + Enumeration entries = targetJarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String name; + String entryName = entry.getName(); + int slashIndex = entryName.indexOf("/"); + if (slashIndex >= 0) { + // handle directory + name = entryName.substring(slashIndex + 1); + String dirName = entryName.substring(0, slashIndex); + assertTrue(mandatoryEntries.containsKey(dirName)); + assertEquals("dir", mandatoryEntries.get(dirName)); + mandatoryEntries.remove(dirName); + } else { + name = entryName; + } + if (mandatoryEntries.containsKey(name)) { + assertEquals("file", (String) mandatoryEntries.get(name)); + assertFalse(entry.isDirectory()); + mandatoryEntries.remove(name); + } + } + assertTrue(mandatoryEntries.size() == 0); + assertNotNull(targetJarFile.getManifest()); + } finally { + targetJarFile.close(); + } + } + + private void createSourceJar() throws FileNotFoundException, IOException { + Manifest manifest = new Manifest(); + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(_sourceJarFile), manifest); + addFile(_contentFile, _contentDir, jarOut); + jarOut.close(); + } + + private void createAdditionalDirectory() throws IOException { + _additionalDir = File.createTempFile("additional", ""); + assertTrue(FileHelper.createTempDirectory(_additionalDir)); + + _additionalFile = new File(_additionalDir, "additional.file"); + _additionalFile.createNewFile(); + assertTrue(_additionalFile.exists()); + } + + private void addFile(File file, File parentDir, JarOutputStream jarOut) throws IOException { + byte[] buffer = new byte[1024]; + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + String jarEntryName = parentDir.getName() + "/" + file.getName(); + jarOut.putNextEntry(new JarEntry(jarEntryName)); + for (int read = 0; read != -1; read = inputStream.read(buffer)) + jarOut.write(buffer, 0, read); + jarOut.closeEntry(); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + +} diff --git a/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java b/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java @@ -0,0 +1,277 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class StartScriptGeneratorTest extends TestCase { + + private static final String AT_DATE = "@DATE"; + + private static final String WIN_CR_LF = StartScriptGenerator.WIN_CR_LF; + + private StartScriptGenerator _generator; + + private File _targetDir; + + protected void setUp() throws Exception { + String userDirName = System.getProperty("user.dir"); // only true in eclipse ? + File userDir = new File(userDirName); + File parentDir = userDir.getParentFile(); + assertTrue(parentDir.exists()); + _targetDir = new File(parentDir, "jython"); + if (!_targetDir.exists()) { + _targetDir = new File(parentDir, "jython-trunk"); + } + assertTrue(_targetDir.exists()); + assertTrue(_targetDir.isDirectory()); + _targetDir = new File(_targetDir, "src"); + _targetDir = new File(_targetDir, "shell"); + assertTrue(_targetDir.exists()); + assertTrue(_targetDir.isDirectory()); + _generator = new StartScriptGenerator(_targetDir, new JavaHomeHandler()); + } + + // TODO: test on Solaris + public void testUnix() throws IOException { + _generator.setFlavour(StartScriptGenerator.UNIX_FLAVOUR); + StringBuffer buf = new StringBuffer(100); + buf.append("#!/usr/bin/env bash\n"); + buf.append("\n"); + buf.append("# This file was generated by the Jython installer\n"); + buf.append("# Created on " + AT_DATE + " by " + System.getProperty("user.name") + "\n"); + buf.append("\n"); + buf.append("JAVA_HOME=\""); + buf.append(System.getProperty("java.home")); + buf.append("\"\n"); + buf.append("JYTHON_HOME_FALLBACK=\""); + buf.append(_targetDir.getAbsolutePath()); + buf.append("\"\n"); + // some rudimentary tests - feel free to do more + String start = buf.toString().replaceAll(AT_DATE, new Date().toString()); + String unixScript = _generator.getJythonScript(StartScriptGenerator.UNIX_FLAVOUR); + assertTrue(unixScript.startsWith(start)); + assertTrue(unixScript.length() > 3500); + assertTrue(unixScript.indexOf("-Dpython.home=") > start.length()); + assertTrue(unixScript.indexOf("-Dpython.executable=") > start.length()); + // no hard coding of JYTHON_HOME + int jythonHomeIndex = unixScript.indexOf("if [ -z \"$JYTHON_HOME\" ] ; then"); + assertTrue(jythonHomeIndex >= 0); + int definitionIndex = unixScript.indexOf("JYTHON_HOME="); + assertTrue(definitionIndex > jythonHomeIndex || definitionIndex < 0); + } + + public void testWindows() throws IOException { + StringBuffer winBuf = new StringBuffer(100); + winBuf.append("@echo off" + WIN_CR_LF); + winBuf.append("rem This file was generated by the Jython installer" + WIN_CR_LF); + winBuf.append("rem Created on " + AT_DATE + " by " + System.getProperty("user.name") + "" + + WIN_CR_LF); + winBuf.append(WIN_CR_LF); + winBuf.append("set JAVA_HOME=\""); + winBuf.append(System.getProperty("java.home")); + winBuf.append("\""); + winBuf.append(WIN_CR_LF); + winBuf.append("set JYTHON_HOME_FALLBACK=\""); + winBuf.append(_targetDir.getAbsolutePath()); + winBuf.append("\""); + winBuf.append(WIN_CR_LF); + // some rudimentary tests - feel free to do more + String start = winBuf.toString().replaceAll(AT_DATE, new Date().toString()); + String winScript = _generator.getJythonScript(StartScriptGenerator.WINDOWS_FLAVOUR); + assertTrue(winScript.startsWith(start)); + assertTrue(winScript.length() > 3500); + assertTrue(winScript.indexOf("if not \"%_TRIMMED_JAVA_HOME%\"==\"\"") > start.length()); + assertTrue(winScript.indexOf("-Dpython.home=") > start.length()); + assertTrue(winScript.indexOf("-Dpython.executable=") > start.length()); + // no hard coding of JYTHON_HOME + int jythonHomeIndex = winScript.indexOf("if not \"%_TRIMMED_JYTHON_HOME%\"==\"\""); + assertTrue(jythonHomeIndex >= 0); + int definitionIndex = winScript.indexOf("set JYTHON_HOME="); + assertTrue(definitionIndex > jythonHomeIndex || definitionIndex < 0); + } + + public void testFlavour() { + int expectedFlavour; + expectedFlavour = StartScriptGenerator.UNIX_FLAVOUR; + _generator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, _generator.getFlavour()); + expectedFlavour = StartScriptGenerator.BOTH_FLAVOUR; + _generator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, _generator.getFlavour()); + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(new File("dummy"), + new JavaHomeHandler("dummy"), + false); + expectedFlavour = StartScriptGenerator.WINDOWS_FLAVOUR; + testGenerator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, testGenerator.getFlavour()); + expectedFlavour = StartScriptGenerator.UNIX_FLAVOUR; + testGenerator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, testGenerator.getFlavour()); + testGenerator = new TestStartScriptGenerator(new File("dummy"), + new JavaHomeHandler("dummy"), + true); + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + assertEquals(StartScriptGenerator.BOTH_FLAVOUR, testGenerator.getFlavour()); + } + + public void testWindowsFlavour() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // windows flavour + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + false); + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); // 1 file plus the /bin subdirectory + HashSet fileNamesSet = new HashSet(2); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython.bat")); + fileNames = bin.list(); + assertEquals(1, fileNames.length); + assertEquals("jython.bat", fileNames[0]); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + public void testUnixFlavour() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // unix flavour + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + false); + testGenerator.setFlavour(StartScriptGenerator.UNIX_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); // 1 file plus the /bin subdirectory + HashSet fileNamesSet = new HashSet(2); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython")); + fileNames = bin.list(); + assertEquals(1, fileNames.length); + assertEquals("jython", fileNames[0]); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + public void testBothFlavours() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // both flavours + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + true); + // test generator constructor timing problem: do set the flavour once again + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(3, fileNamesLength); // 2 files plus the /bin subdirectory + Set fileNamesSet = new HashSet(4); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython")); + assertTrue(fileNamesSet.contains("jython.bat")); + fileNames = bin.list(); + fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); + fileNamesSet = new HashSet(4); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("jython")); + assertTrue(fileNamesSet.contains("jython.bat")); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + class TestStartScriptGenerator extends StartScriptGenerator { + + private boolean _hasBothFlavours; + + public TestStartScriptGenerator(File targetDirectory, + JavaHomeHandler javaHomeHandler, + boolean hasBothFlavours) { + super(targetDirectory, javaHomeHandler); + _hasBothFlavours = hasBothFlavours; + } + + protected boolean hasUnixlikeShell() { + return _hasBothFlavours; + } + } +} diff --git a/installer/test/java/org/python/util/install/UnicodeSequencesTest.java b/installer/test/java/org/python/util/install/UnicodeSequencesTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/UnicodeSequencesTest.java @@ -0,0 +1,34 @@ +package org.python.util.install; + +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class UnicodeSequencesTest extends TestCase { + + private static Set _latin1Encodings; + + public void testUmlaute() { + String fileEncoding = System.getProperty("file.encoding", "unknown"); + if (getLatin1Encodings().contains(fileEncoding)) { + assertEquals("?", UnicodeSequences.a2); + assertEquals("?", UnicodeSequences.A2); + assertEquals("?", UnicodeSequences.o2); + assertEquals("?", UnicodeSequences.O2); + assertEquals("?", UnicodeSequences.u2); + assertEquals("?", UnicodeSequences.U2); + } + } + + private static Set getLatin1Encodings() { + if (_latin1Encodings == null) { + _latin1Encodings = new HashSet(3); + _latin1Encodings.add("ISO-LATIN-1"); + _latin1Encodings.add("ISO-8859-1"); + _latin1Encodings.add("Cp1252"); + } + return _latin1Encodings; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/AutotestTest.java b/installer/test/java/org/python/util/install/driver/AutotestTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/AutotestTest.java @@ -0,0 +1,78 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.InstallerCommandLine; + +import junit.framework.TestCase; + +public class AutotestTest extends TestCase { + + private Autotest _autotest; + + protected void setUp() throws Exception { + InstallerCommandLine commandLine = new InstallerCommandLine(); + commandLine.setArgs(new String[0]); + _autotest = new SilentAutotest(commandLine); + } + + public void testCreateDirectories() { + File rootDir = Autotest.getRootDir(); + File targetDir = _autotest.getTargetDir(); + assertNotNull(rootDir); + verifyDir(rootDir, false); + assertNotNull(targetDir); + verifyDir(targetDir, true); + assertEquals(rootDir, targetDir.getParentFile()); + } + + public void testCommandLineArgs() { + String[] args = new String[] { "-x", "-y", "-z" }; + _autotest.setCommandLineArgs(args); + int len = _autotest.getCommandLineArgs().length; + assertEquals(args.length, len); + for (int i = 0; i < args.length; i++) { + assertEquals(args[i], _autotest.getCommandLineArgs()[i]); + } + } + + public void testAddArgument() { + String[] args = new String[] { "-x", "-y", "-z" }; + _autotest.setCommandLineArgs(args); + _autotest.addArgument("-u"); + assertEquals(args.length + 1, _autotest.getCommandLineArgs().length); + for (int i = 0; i < args.length; i++) { + assertEquals(args[i], _autotest.getCommandLineArgs()[i]); + } + assertEquals("-u", _autotest.getCommandLineArgs()[args.length]); + } + + public void testVerify() throws Exception { + TestVerifier testVerifier = new TestVerifier(); + _autotest.setVerifier(testVerifier); + assertNotNull(_autotest.getVerifier()); + assertNotNull(_autotest.getVerifier().getTargetDir()); + assertEquals(_autotest.getTargetDir(), testVerifier.getTargetDir()); + try { + _autotest.getVerifier().verify(); + fail("should have thrown"); + } catch (DriverException de) { + } + + } + + private void verifyDir(File dir, boolean ensureEmpty) { + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + if (ensureEmpty) { + assertTrue(dir.listFiles().length <= 0); + } + } + + private static class TestVerifier extends NormalVerifier { + public void verify() throws DriverException { + throw new DriverException("test verification failure"); + } + } + +} diff --git a/installer/test/java/org/python/util/install/driver/DrivableConsole.java b/installer/test/java/org/python/util/install/driver/DrivableConsole.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/DrivableConsole.java @@ -0,0 +1,82 @@ +package org.python.util.install.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.python.util.install.driver.Tunnel; + +/** + * A simple class performing console I/O, easy to test. + */ +public class DrivableConsole { + + private static final String _PROMPT = ">>>"; + private Tunnel _tunnel; + + public DrivableConsole(Tunnel tunnel) { + _tunnel = tunnel; + } + + /** + * The console logic. + */ + public void handleConsoleIO() throws Exception { + String answer; + answer = question("first question"); + if ("1".equals(answer)) { + System.out.println("answer1 is " + answer); + answer = question("second question"); + if ("2".equals(answer)) { + System.out.println("answer2 is " + answer); + answer = question("third question"); + if ("3".equals(answer)) { + System.out.println("answer3 is " + answer); + } else { + throw new Exception("wrong answer3: " + answer); + } + } else { + throw new Exception("wrong answer2: " + answer); + } + } else { + throw new Exception("wrong answer1: " + answer); + } + } + + /** + * Write a question (to normal System.out) + */ + private String question(String question) throws IOException { + question = question + " " + _PROMPT + " "; + String answer = ""; + // output to normal System.out + System.out.print(question); // intended print, not println (!) + answer = readLine(); + return answer; + } + + /** + * Send a signal through the tunnel, and then wait for the answer from the other side. + * + *
+     *     (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *     (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * 
+ */ + private String readLine() throws IOException { + InputStream inputStream; + String line = ""; + if (_tunnel == null) { + inputStream = System.in; + } else { + inputStream = _tunnel.getAnswerReceiverStream(); + _tunnel.getQuestionSenderStream().write(Tunnel.NEW_LINE.getBytes()); + _tunnel.getQuestionSenderStream().flush(); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + line = reader.readLine(); + return line; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java b/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java @@ -0,0 +1,37 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.python.util.install.driver.ConsoleDriver; +import org.python.util.install.driver.Tunnel; + +import junit.framework.TestCase; + +public class DrivableConsoleTest extends TestCase { + + private DrivableConsole _console; + private Tunnel _tunnel; + + protected void setUp() throws IOException { + _tunnel = new Tunnel(); + _console = new DrivableConsole(_tunnel); + } + + public void testDrive() throws Exception { + // sequence matters here (have to fork off the driver thread first + ConsoleDriver driver = new ConsoleDriver(_tunnel, getAnswers()); + driver.start(); + _console.handleConsoleIO(); + } + + private Collection getAnswers() { + Collection answers = new ArrayList(); + answers.add("1"); + answers.add("2"); + answers.add("3"); + return answers; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java b/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java @@ -0,0 +1,131 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.JavaVersionTester; + +public class NormalVerifierTest extends TestCase { + + private static final String DQ = "\""; + + private NormalVerifier _verifier; + + protected void setUp() throws Exception { + super.setUp(); + _verifier = new NormalVerifier(); + // use a directory containing spaces as target directory + File targetDir = createTargetDirectory(); + assertTrue(targetDir.exists()); + assertTrue(targetDir.isDirectory()); + _verifier.setTargetDir(targetDir); + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (_verifier.getTargetDir() != null) { + File autotestFile = new File(_verifier.getTargetDir().getCanonicalPath(), + NormalVerifier.AUTOTEST_PY); + if (autotestFile.exists()) { + assertTrue(autotestFile.delete()); + } + } + } + + // have to install jython first in order to activate this test + public void testVerify() throws Exception {} + + public void testGetSimpleCommand() throws Exception { + String prefix = _verifier.getTargetDir().getCanonicalPath().concat(File.separator); + String expectedCommand = prefix.concat("jython"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".bat"); + } + String expectedArgument = prefix.concat("autotest.py"); + String[] command = _verifier.getSimpleCommand(); + assertNotNull(command); + assertEquals(2, command.length); + assertEquals(expectedCommand, command[0]); + assertEquals(expectedArgument, command[1]); + } + + public void testDoShellScriptTests() { + assertTrue(_verifier.doShellScriptTests()); + } + + public void testGetShellScriptTestCommandDir() throws DriverException, IOException { + String expectedDir = _verifier.getTargetDir() + .getCanonicalPath() + .concat(File.separator) + .concat("bin"); + assertEquals(expectedDir, _verifier.getShellScriptTestCommandDir().getCanonicalPath()); + } + + public void testGetShellScriptTestContents() throws Exception { + String contents = _verifier.getShellScriptTestContents(); + // common asserts + assertNotNull(contents); + assertFalse(contents.length() == 0); + assertFalse(contents.indexOf("{0}") > 0); + assertFalse(contents.indexOf("{1}") > 0); + assertFalse(contents.indexOf("{2}") > 0); + assertFalse(contents.indexOf("{3}") > 0); + assertTrue(contents.indexOf("autotest.py") > 0); + String targetDirPath = _verifier.getTargetDir().getCanonicalPath(); + String upScriptPath = _verifier.getSimpleCommand()[1]; + String javaHome = System.getProperty(JavaVersionTester.JAVA_HOME, ""); // change this ++++++ + assertTrue(javaHome.length() > 0); + // platform specific asserts + if (Installation.isWindows()) { + assertTrue(contents.indexOf("set _INSTALL_DIR=") > 0); + assertTrue(contents.indexOf("set _INSTALL_DIR=".concat(targetDirPath)) > 0); + assertTrue(contents.indexOf("set _SCRIPT=") > 0); + assertTrue(contents.indexOf("set _SCRIPT=".concat(upScriptPath)) > 0); + assertTrue(contents.indexOf("set _JAVA_HOME=") > 0); + assertTrue(contents.indexOf("set _JAVA_HOME=".concat(javaHome)) > 0); + } else { + System.out.println(contents); + assertTrue(contents.indexOf("_INSTALL_DIR=") > 0); + assertTrue(contents.indexOf("_INSTALL_DIR=".concat(quote(targetDirPath))) > 0); + assertTrue(contents.indexOf("_SCRIPT=") > 0); + assertTrue(contents.indexOf("_SCRIPT=".concat(quote(upScriptPath))) > 0); + assertTrue(contents.indexOf("_JAVA_HOME=") > 0); + assertTrue(contents.indexOf("_JAVA_HOME=".concat(quote(javaHome))) > 0); + } + } + + public void testGetShellScriptTestCommand() throws Exception { + String prefix = _verifier.getShellScriptTestCommandDir() + .getCanonicalPath() + .concat(File.separator); + String expectedCommand = prefix.concat("jython_test"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".bat"); + } + String[] command = _verifier.getShellScriptTestCommand(); + assertNotNull(command); + assertEquals(1, command.length); + String commandFileName = command[0]; + assertEquals(expectedCommand, commandFileName); + File commandFile = new File(commandFileName); + assertTrue(commandFile.exists()); + String contents = FileHelper.readAll(commandFile); + assertNotNull(contents); + assertFalse(contents.length() == 0); + assertEquals(_verifier.getShellScriptTestContents(), contents); + } + + private File createTargetDirectory() throws IOException { + File tmpFile = File.createTempFile("NormalVerifierTest_", "with spaces"); + FileHelper.createTempDirectory(tmpFile); + return tmpFile; + } + + private String quote(String value) { + return DQ.concat(value).concat(DQ); + } +} diff --git a/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java b/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java @@ -0,0 +1,72 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.Installation; +import org.python.util.install.JarInstaller; + +import junit.framework.TestCase; + +public class StandaloneVerifierTest extends TestCase { + + private StandaloneVerifier _verifier; + + protected void setUp() throws Exception { + super.setUp(); + _verifier = new StandaloneVerifier(); + File targetDir = null; + // have to install jython first in order to activate this test + // targetDir = new File("C:/Temp/jython.autoinstall.root_54159_dir/006 + // consoleTest_54165_dir"); + _verifier.setTargetDir(targetDir); + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (_verifier.getTargetDir() != null) { + File autotestFile = new File(_verifier.getTargetDir().getCanonicalPath(), + StandaloneVerifier.AUTOTEST_PY); + if (autotestFile.exists()) { + assertTrue(autotestFile.delete()); + } + } + } + + public void testVerify() throws Exception { + if (_verifier.getTargetDir() != null) { + _verifier.verify(); + } + } + + public void testGetSimpleCommand() throws Exception { + File javaHome = new File(System.getProperty("java.home")); + assertNotNull(javaHome); + assertTrue(javaHome.exists()); + File targetDir = new File(System.getProperty(("user.dir"))); // any existing dir + assertNotNull(targetDir); + assertTrue(targetDir.exists()); + String prefix = targetDir.getCanonicalPath().concat(File.separator); + String expectedCommand = javaHome.getCanonicalPath() + .concat(File.separator) + .concat("bin") + .concat(File.separator) + .concat("java"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".exe"); + } + String expectedArgument = prefix.concat("autotest.py"); + _verifier.setTargetDir(targetDir); + String[] command = _verifier.getSimpleCommand(); + assertNotNull(command); + assertEquals(4, command.length); + assertEquals(expectedCommand, command[0]); + assertEquals("-jar", command[1]); + assertEquals(prefix.concat(JarInstaller.JYTHON_JAR), command[2]); + assertEquals(expectedArgument, command[3]); + } + + public void testDoShellScriptTests() { + // we cannot do shell script tests in standalone mode + assertFalse(_verifier.doShellScriptTests()); + } +} -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 02:05:46 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 9 Feb 2013 02:05:46 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_Remove_all_svn_?= =?utf-8?q?dependencies=2E?= Message-ID: <3Z2w6y2zHZzSTN@mail.python.org> http://hg.python.org/jython/rev/f99964642fa1 changeset: 7016:f99964642fa1 branch: 2.5 user: Frank Wierzbicki date: Fri Feb 08 14:00:35 2013 -0800 summary: Remove all svn dependencies. files: build.xml | 88 +--------- extlibs/svnant-jars/svnClientAdapter.jar | Bin extlibs/svnant-jars/svnant.jar | Bin extlibs/svnant-jars/svnjavahl.jar | Bin maven/build.xml | 6 +- 5 files changed, 16 insertions(+), 78 deletions(-) diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -9,16 +9,16 @@ This build will create directories /build and /dist below basedir. -Use case 2: full build for a release (using svn checkout) +Use case 2: full build for a release (using hg checkout) --------------------------------------------------------- - - make sure you have access to the Jython Subversion repository - (https://jython.svn.sourceforge.net/svnroot/jython/trunk) - - override svn.main.dir in ant.properties (if necessary) + - make sure you have access to the Jython mercurial repository + (http://hg.python.org/jython) + - override ant.properties (if necessary) - call target 'full-build' This build will create a working directory named -full_build/${svn.main.dir} at the same level as your local directories -jython, sandbox and installer. It will contain a big +full_build at the same level as your local directories +jython and installer. It will contain a big jython_installer-${jython.version}.jar file suitable for installation. To build older releases, it may be necessary to use an older @@ -58,45 +58,6 @@ #debug=false #deprecation=off -# - the svn main directory to build from; only needed for full-build -# This e.g. could be one of: -# trunk -# branches/Release_2_2maint -# tags/Release_2_2rc3 -# meaning any directory just above the two directories: -# /installer -# /jython -# svn.main.dir defaults to trunk -#svn.main.dir=trunk - -# - the revision; only needed for a snapshot full-build -# To create a snapshot build: uncomment the two revision lines, and indicate the correct revision (it has to be a number) -# For 'normal' builds, this defaults to the latest revision on svn.main.dir (HEAD) -#svn.revision=7114 -#snapshot.revision=${svn.revision} - -# - the directory containing libsvnjavahl-1.dll (on windows) and svnjavahl.jar; only needed for full-build -# how to get these (for windows): -# - goto http://subversion.tigris.org/servlets/ProjectDocumentList -# - open the Releases folder -# - click on the Windows folder -# - download svn-win32-1.4.6_javahl.zip (or newer) -# - unzip the .dll and .jar into javahl.dir -javahl.dir=C:/Programme/Subversion/javahl - -# - the directory containing the svnant related .jar files; only needed for full-build -# the following .jar files (probably) are needed: -# - commons-lang-2.0.jar -# - jakarta-regexp-1.3.jar -# - svnant.jar -# - svnClientAdapter.jar -# - svnjavahl.jar -# how to get these: -# - goto http://subclipse.tigris.org/servlets/ProjectDocumentList -# - click on the the svnant folder -# - download svnant-1.0.0.zip (or newer) -# - unzip the jar files from the /lib folder to svnant.jar.dir -svnant.jar.dir=${basedir}/../externals/svnant-jars @@ -104,7 +65,7 @@ - + @@ -235,18 +196,14 @@ - - - - @@ -259,15 +216,6 @@
- - - - - - - - - @@ -302,11 +250,8 @@ debug = '${debug}' nowarn = '${nowarn}' --- properties (used for full-build only) --- - svn.main.dir = '${svn.main.dir}' - svn.revision = '${svn.revision}' checkout.dir = '${checkout.dir}' javahl.dir = '${javahl.dir}' - svnant.jar.dir = '${svnant.jar.dir}' do.snapshot.build = '${do.snapshot.build}' snapshot.revision = '${snapshot.revision}' do.checkout = '${do.checkout}' @@ -390,15 +335,6 @@ - - - - - - - - - @@ -458,7 +394,7 @@ + value='2.5a${xxx.revision}' /> ======================= -------------------------- @@ -665,7 +601,6 @@
- @@ -688,7 +623,7 @@
- + @@ -724,7 +659,7 @@
- + @@ -920,7 +855,6 @@ - @@ -940,7 +874,7 @@
- + diff --git a/extlibs/svnant-jars/svnClientAdapter.jar b/extlibs/svnant-jars/svnClientAdapter.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnClientAdapter.jar has changed diff --git a/extlibs/svnant-jars/svnant.jar b/extlibs/svnant-jars/svnant.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnant.jar has changed diff --git a/extlibs/svnant-jars/svnjavahl.jar b/extlibs/svnant-jars/svnjavahl.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnjavahl.jar has changed diff --git a/maven/build.xml b/maven/build.xml --- a/maven/build.xml +++ b/maven/build.xml @@ -67,9 +67,13 @@ + + + + - + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 02:05:47 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 9 Feb 2013 02:05:47 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_Integrate_insta?= =?utf-8?q?ller_into_dev_build_and_maven_build=2E?= Message-ID: <3Z2w6z6fqszSPj@mail.python.org> http://hg.python.org/jython/rev/041760b4c0d7 changeset: 7017:041760b4c0d7 branch: 2.5 user: Frank Wierzbicki date: Fri Feb 08 16:27:49 2013 -0800 summary: Integrate installer into dev build and maven build. files: README.txt | 4 ++-- build.xml | 24 ++++++++++++------------ maven/build.xml | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.txt b/README.txt --- a/README.txt +++ b/README.txt @@ -1,5 +1,5 @@ -Welcome to Jython 2.5.3! -======================== +Welcome to Jython 2.5.4rc1! +=========================== This is the first release candidate of Jython 2.5.4. diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -65,7 +65,7 @@ - + @@ -690,7 +690,7 @@ - + @@ -811,17 +811,17 @@ - - + - - - compiling installer from ${install.src.dir} - + + compiling installer from ${installer.src.dir} + - + copy installer icon to ${dist.dir} - + @@ -848,7 +848,7 @@ building installer .jar file - + @@ -867,7 +867,7 @@ - +
diff --git a/maven/build.xml b/maven/build.xml --- a/maven/build.xml +++ b/maven/build.xml @@ -32,7 +32,7 @@ - + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 02:05:52 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 9 Feb 2013 02:05:52 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z2w742LCGzRdk@mail.python.org> http://hg.python.org/jython/rev/3b424500eab9 changeset: 7018:3b424500eab9 parent: 7010:f29453ef0415 parent: 7017:041760b4c0d7 user: Frank Wierzbicki date: Fri Feb 08 17:04:23 2013 -0800 summary: Merge 2.5. files: .hgtags | 1 + README.txt | 11 +- build.xml | 116 +- extlibs/svnant-jars/svnClientAdapter.jar | Bin extlibs/svnant-jars/svnant.jar | Bin extlibs/svnant-jars/svnjavahl.jar | Bin installer/src/java/org/apache/LICENSE.txt | 202 +++ installer/src/java/org/apache/commons/cli/AlreadySelectedException.java | 81 + installer/src/java/org/apache/commons/cli/BasicParser.java | 92 + installer/src/java/org/apache/commons/cli/CommandLine.java | 328 +++++ installer/src/java/org/apache/commons/cli/CommandLineParser.java | 97 + installer/src/java/org/apache/commons/cli/GnuParser.java | 187 ++ installer/src/java/org/apache/commons/cli/HelpFormatter.java | 542 ++++++++ installer/src/java/org/apache/commons/cli/MissingArgumentException.java | 82 + installer/src/java/org/apache/commons/cli/MissingOptionException.java | 81 + installer/src/java/org/apache/commons/cli/Option.java | 575 +++++++++ installer/src/java/org/apache/commons/cli/OptionBuilder.java | 368 +++++ installer/src/java/org/apache/commons/cli/OptionGroup.java | 187 ++ installer/src/java/org/apache/commons/cli/Options.java | 331 +++++ installer/src/java/org/apache/commons/cli/ParseException.java | 82 + installer/src/java/org/apache/commons/cli/Parser.java | 282 ++++ installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java | 204 +++ installer/src/java/org/apache/commons/cli/PosixParser.java | 342 +++++ installer/src/java/org/apache/commons/cli/TypeHandler.java | 252 +++ installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java | 82 + installer/src/java/org/python/util/install/AbstractWizard.java | 438 ++++++ installer/src/java/org/python/util/install/AbstractWizardHeader.java | 12 + installer/src/java/org/python/util/install/AbstractWizardPage.java | 153 ++ installer/src/java/org/python/util/install/AbstractWizardValidator.java | 132 ++ installer/src/java/org/python/util/install/ChildProcess.java | 357 +++++ installer/src/java/org/python/util/install/ConsoleInstaller.java | 609 +++++++++ installer/src/java/org/python/util/install/DirectoryFilter.java | 23 + installer/src/java/org/python/util/install/DirectorySelectionPage.java | 200 +++ installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java | 36 + installer/src/java/org/python/util/install/EmptyValidator.java | 8 + installer/src/java/org/python/util/install/FileHelper.java | 208 +++ installer/src/java/org/python/util/install/FrameInstaller.java | 186 ++ installer/src/java/org/python/util/install/Installation.java | 439 ++++++ installer/src/java/org/python/util/install/InstallationCancelledException.java | 9 + installer/src/java/org/python/util/install/InstallationListener.java | 7 + installer/src/java/org/python/util/install/InstallationType.java | 120 + installer/src/java/org/python/util/install/InstallerCommandLine.java | 456 +++++++ installer/src/java/org/python/util/install/InstallerException.java | 21 + installer/src/java/org/python/util/install/JarInfo.java | 235 +++ installer/src/java/org/python/util/install/JarInstaller.java | 283 ++++ installer/src/java/org/python/util/install/JavaHomeHandler.java | 209 +++ installer/src/java/org/python/util/install/JavaSelectionPage.java | 199 +++ installer/src/java/org/python/util/install/JavaSelectionPageValidator.java | 31 + installer/src/java/org/python/util/install/JavaVersionTester.java | 61 + installer/src/java/org/python/util/install/LanguagePage.java | 121 + installer/src/java/org/python/util/install/LicensePage.java | 120 + installer/src/java/org/python/util/install/LicensePageValidator.java | 17 + installer/src/java/org/python/util/install/OverviewPage.java | 243 +++ installer/src/java/org/python/util/install/ProgressListener.java | 15 + installer/src/java/org/python/util/install/ProgressPage.java | 122 + installer/src/java/org/python/util/install/ReadmePage.java | 75 + installer/src/java/org/python/util/install/StandalonePackager.java | 184 ++ installer/src/java/org/python/util/install/StartScriptGenerator.java | 240 +++ installer/src/java/org/python/util/install/SuccessPage.java | 55 + installer/src/java/org/python/util/install/TextConstants.java | 148 ++ installer/src/java/org/python/util/install/TextConstants_de.java | 148 ++ installer/src/java/org/python/util/install/TextConstants_en.java | 5 + installer/src/java/org/python/util/install/TextKeys.java | 135 ++ installer/src/java/org/python/util/install/TypePage.java | 268 ++++ installer/src/java/org/python/util/install/UnicodeSequences.java | 19 + installer/src/java/org/python/util/install/ValidationEvent.java | 14 + installer/src/java/org/python/util/install/ValidationException.java | 19 + installer/src/java/org/python/util/install/ValidationInformationException.java | 21 + installer/src/java/org/python/util/install/ValidationListener.java | 11 + installer/src/java/org/python/util/install/Wizard.java | 90 + installer/src/java/org/python/util/install/WizardEvent.java | 13 + installer/src/java/org/python/util/install/WizardHeader.java | 89 + installer/src/java/org/python/util/install/WizardListener.java | 13 + installer/src/java/org/python/util/install/driver/Autotest.java | 274 ++++ installer/src/java/org/python/util/install/driver/ConsoleAutotest.java | 30 + installer/src/java/org/python/util/install/driver/ConsoleDriver.java | 58 + installer/src/java/org/python/util/install/driver/DriverException.java | 17 + installer/src/java/org/python/util/install/driver/GuiAutotest.java | 188 ++ installer/src/java/org/python/util/install/driver/InstallationDriver.java | 416 ++++++ installer/src/java/org/python/util/install/driver/NormalVerifier.java | 287 ++++ installer/src/java/org/python/util/install/driver/SilentAutotest.java | 25 + installer/src/java/org/python/util/install/driver/StandaloneVerifier.java | 74 + installer/src/java/org/python/util/install/driver/Tunnel.java | 61 + installer/src/java/org/python/util/install/driver/Verifier.java | 17 + installer/src/java/org/python/util/install/driver/jython_test.bat.template | 73 + installer/src/java/org/python/util/install/driver/jython_test.template | 58 + installer/src/java/org/python/util/install/jython_small_c.png | Bin installer/test/java/org/AllTests.java | 101 + installer/test/java/org/apache/commons/cli/ApplicationTest.java | 120 + installer/test/java/org/apache/commons/cli/BugsTest.java | 348 +++++ installer/test/java/org/apache/commons/cli/BuildTest.java | 96 + installer/test/java/org/apache/commons/cli/GnuParseTest.java | 265 ++++ installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java | 106 + installer/test/java/org/apache/commons/cli/HelpFormatterTest.java | 82 + installer/test/java/org/apache/commons/cli/OptionBuilderTest.java | 160 ++ installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java | 43 + installer/test/java/org/apache/commons/cli/OptionGroupTest.java | 246 +++ installer/test/java/org/apache/commons/cli/OptionsTest.java | 28 + installer/test/java/org/apache/commons/cli/ParseRequiredTest.java | 113 + installer/test/java/org/apache/commons/cli/ParseTest.java | 285 ++++ installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java | 82 + installer/test/java/org/apache/commons/cli/PosixParserTest.java | 138 ++ installer/test/java/org/apache/commons/cli/TestHelpFormatter.java | 170 ++ installer/test/java/org/apache/commons/cli/ValueTest.java | 271 ++++ installer/test/java/org/apache/commons/cli/ValuesTest.java | 248 +++ installer/test/java/org/python/util/install/ChildProcessExample.java | 32 + installer/test/java/org/python/util/install/ChildProcessTest.java | 80 + installer/test/java/org/python/util/install/ChmodTest_Standalone.java | 57 + installer/test/java/org/python/util/install/FileHelperTest.java | 271 ++++ installer/test/java/org/python/util/install/FrameInstallerTest.java | 88 + installer/test/java/org/python/util/install/InstallationTest.java | 107 + installer/test/java/org/python/util/install/InstallationTypeTest.java | 119 + installer/test/java/org/python/util/install/InstallerCommandLineTest.java | 637 ++++++++++ installer/test/java/org/python/util/install/JavaHomeHandlerTest.java | 114 + installer/test/java/org/python/util/install/JavaTest_Standalone.java | 32 + installer/test/java/org/python/util/install/StandalonePackagerTest.java | 183 ++ installer/test/java/org/python/util/install/StartScriptGeneratorTest.java | 277 ++++ installer/test/java/org/python/util/install/UnicodeSequencesTest.java | 34 + installer/test/java/org/python/util/install/driver/AutotestTest.java | 78 + installer/test/java/org/python/util/install/driver/DrivableConsole.java | 82 + installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java | 37 + installer/test/java/org/python/util/install/driver/NormalVerifierTest.java | 131 ++ installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java | 72 + maven/build.xml | 6 +- 124 files changed, 17961 insertions(+), 98 deletions(-) diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -63,3 +63,4 @@ 4f5e3c12edc040058d724d4469a53e8649e64420 v2.7.0a1 ac5609677c1382f14d0e04178fe6dd4108e4c231 v2.7.0a2 3d2dbae23c5292b7f31ac4c92fa6d1afd8bd8eb2 v2.5.3 +f5b12dc4ff970c9594c99fc895772958c4a669a0 v2.5.4rc1 diff --git a/README.txt b/README.txt --- a/README.txt +++ b/README.txt @@ -1,15 +1,14 @@ -Welcome to Jython 2.7.0a2 -========================= +Welcome to Jython 2.7b1 +======================= -This is the second alpha release of the 2.7.0 version of Jython. Thanks to +This is the first beta release of the 2.7 version of Jython. Thanks to Adconion Media Group (http://www.adconion.com/) for sponsoring this release. Thanks to all who contributed to Jython. -This release fixes a bug that left site-packages out of the path. This caused -many problems, including a failure of ez_setup.py to work. +This release TODO Please see the NEWS file for detailed release notes. -The release was compiled on Ubuntu with JDK 6 and requires JDK 6 to run. +The release was compiled on Ubuntu with JDK 7 and requires JDK 6 to run. Please try this out and report any bugs at http://bugs.jython.org. diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -9,16 +9,16 @@ This build will create directories /build and /dist below basedir. -Use case 2: full build for a release (using svn checkout) +Use case 2: full build for a release (using hg checkout) --------------------------------------------------------- - - make sure you have access to the Jython Subversion repository - (https://jython.svn.sourceforge.net/svnroot/jython/trunk) - - override svn.main.dir in ant.properties (if necessary) + - make sure you have access to the Jython mercurial repository + (http://hg.python.org/jython) + - override ant.properties (if necessary) - call target 'full-build' This build will create a working directory named -full_build/${svn.main.dir} at the same level as your local directories -jython, sandbox and installer. It will contain a big +full_build at the same level as your local directories +jython and installer. It will contain a big jython_installer-${jython.version}.jar file suitable for installation. To build older releases, it may be necessary to use an older @@ -58,45 +58,6 @@ #debug=false #deprecation=off -# - the svn main directory to build from; only needed for full-build -# This e.g. could be one of: -# trunk -# branches/Release_2_2maint -# tags/Release_2_2rc3 -# meaning any directory just above the two directories: -# /installer -# /jython -# svn.main.dir defaults to trunk -#svn.main.dir=trunk - -# - the revision; only needed for a snapshot full-build -# To create a snapshot build: uncomment the two revision lines, and indicate the correct revision (it has to be a number) -# For 'normal' builds, this defaults to the latest revision on svn.main.dir (HEAD) -#svn.revision=7114 -#snapshot.revision=${svn.revision} - -# - the directory containing libsvnjavahl-1.dll (on windows) and svnjavahl.jar; only needed for full-build -# how to get these (for windows): -# - goto http://subversion.tigris.org/servlets/ProjectDocumentList -# - open the Releases folder -# - click on the Windows folder -# - download svn-win32-1.4.6_javahl.zip (or newer) -# - unzip the .dll and .jar into javahl.dir -javahl.dir=C:/Programme/Subversion/javahl - -# - the directory containing the svnant related .jar files; only needed for full-build -# the following .jar files (probably) are needed: -# - commons-lang-2.0.jar -# - jakarta-regexp-1.3.jar -# - svnant.jar -# - svnClientAdapter.jar -# - svnjavahl.jar -# how to get these: -# - goto http://subclipse.tigris.org/servlets/ProjectDocumentList -# - click on the the svnant folder -# - download svnant-1.0.0.zip (or newer) -# - unzip the jar files from the /lib folder to svnant.jar.dir -svnant.jar.dir=${basedir}/../externals/svnant-jars @@ -104,7 +65,7 @@ - + @@ -123,12 +84,12 @@ - - + + - + @@ -248,18 +209,14 @@ - - - - @@ -272,15 +229,6 @@ - - - - - - - - - @@ -316,11 +264,8 @@ debug = '${debug}' nowarn = '${nowarn}' --- properties (used for full-build only) --- - svn.main.dir = '${svn.main.dir}' - svn.revision = '${svn.revision}' checkout.dir = '${checkout.dir}' javahl.dir = '${javahl.dir}' - svnant.jar.dir = '${svnant.jar.dir}' do.snapshot.build = '${do.snapshot.build}' snapshot.revision = '${snapshot.revision}' do.checkout = '${do.checkout}' @@ -407,15 +352,6 @@ --> - - - - - - - - - @@ -475,7 +411,7 @@ + value='2.7a${xxx.revision}' /> ======================= -------------------------- @@ -683,7 +619,6 @@
- @@ -706,7 +641,7 @@
- + @@ -742,7 +677,7 @@
- + @@ -773,7 +708,7 @@ - + @@ -894,17 +829,17 @@ - - + - - - compiling installer from ${install.src.dir} - + + compiling installer from ${installer.src.dir} + - + copy installer icon to ${dist.dir} - + @@ -931,14 +866,13 @@ building installer .jar file - + - @@ -950,14 +884,14 @@ - +
- + diff --git a/extlibs/svnant-jars/svnClientAdapter.jar b/extlibs/svnant-jars/svnClientAdapter.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnClientAdapter.jar has changed diff --git a/extlibs/svnant-jars/svnant.jar b/extlibs/svnant-jars/svnant.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnant.jar has changed diff --git a/extlibs/svnant-jars/svnjavahl.jar b/extlibs/svnant-jars/svnjavahl.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnjavahl.jar has changed diff --git a/installer/src/java/org/apache/LICENSE.txt b/installer/src/java/org/apache/LICENSE.txt new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/LICENSE.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java b/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java @@ -0,0 +1,81 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + *

Thrown when more than one option in an option group + * has been provided.

+ * + * @author John Keyes ( john at integralsource.com ) + * @see ParseException + */ +public class AlreadySelectedException extends ParseException { + + /** + *

Construct a new AlreadySelectedException + * with the specified detail message.

+ * + * @param message the detail message + */ + public AlreadySelectedException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/BasicParser.java b/installer/src/java/org/apache/commons/cli/BasicParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/BasicParser.java @@ -0,0 +1,92 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + * The class BasicParser provides a very simple implementation of + * the {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + */ +public class BasicParser extends Parser { + + /** + *

A simple implementation of {@link Parser}'s abstract + * {@link Parser#flatten(Options,String[],boolean) flatten} method.

+ * + *

Note: options and stopAtNonOption + * are not used in this flatten method.

+ * + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed + * @param stopAtNonOption Specifies whether to stop flattening + * when an non option is found. + * @return The arguments String array. + */ + protected String[] flatten( Options options, + String[] arguments, + boolean stopAtNonOption ) + { + // just echo the arguments + return arguments; + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/CommandLine.java b/installer/src/java/org/apache/commons/cli/CommandLine.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/CommandLine.java @@ -0,0 +1,328 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; + +/** + *

Represents list of arguments parsed against + * a {@link Options} descriptor.

+ * + *

It allows querying of a boolean {@link #hasOption(String opt)}, + * in addition to retrieving the {@link #getOptionValue(String opt)} + * for options requiring arguments.

+ * + *

Additionally, any left-over or unrecognized arguments, + * are available for further processing.

+ * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @author John Keyes (john at integralsource.com) + */ +public class CommandLine { + + /** the unrecognised options/arguments */ + private List args = new LinkedList(); + + /** the processed options */ + private Map options = new HashMap(); + + /** Map of unique options for ease to get complete list of options */ + private Map hashcodeMap = new HashMap(); + + /** the processed options */ + private Option[] optionsArray; + + /** + *

Creates a command line.

+ */ + CommandLine() { + } + + /** + *

Query to see if an option has been set.

+ * + * @param opt Short name of the option + * @return true if set, false if not + */ + public boolean hasOption(String opt) { + return options.containsKey( opt ); + } + + /** + *

Query to see if an option has been set.

+ * + * @param opt character name of the option + * @return true if set, false if not + */ + public boolean hasOption( char opt ) { + return hasOption( String.valueOf( opt ) ); + } + + /** + *

Return the Object type of this Option.

+ * + * @param opt the name of the option + * @return the type of this Option + */ + public Object getOptionObject( String opt ) { + String res = getOptionValue( opt ); + + Object type = ((Option)((List)options.get(opt)).iterator().next()).getType(); + return res == null ? null : TypeHandler.createValue(res, type); + } + + /** + *

Return the Object type of this Option.

+ * + * @param opt the name of the option + * @return the type of opt + */ + public Object getOptionObject( char opt ) { + return getOptionObject( String.valueOf( opt ) ); + } + + /** + *

Retrieve the argument, if any, of this option.

+ * + * @param opt the name of the option + * @return Value of the argument if option is set, and has an argument, + * otherwise null. + */ + public String getOptionValue( String opt ) { + String[] values = getOptionValues(opt); + return (values == null) ? null : values[0]; + } + + /** + *

Retrieve the argument, if any, of this option.

+ * + * @param opt the character name of the option + * @return Value of the argument if option is set, and has an argument, + * otherwise null. + */ + public String getOptionValue( char opt ) { + return getOptionValue( String.valueOf( opt ) ); + } + + /** + *

Retrieves the array of values, if any, of an option.

+ * + * @param opt string name of the option + * @return Values of the argument if option is set, and has an argument, + * otherwise null. + */ + public String[] getOptionValues( String opt ) { + List values = new java.util.ArrayList(); + + if( options.containsKey( opt ) ) { + List opts = (List)options.get( opt ); + Iterator iter = opts.iterator(); + + while( iter.hasNext() ) { + Option optt = (Option)iter.next(); + values.addAll( optt.getValuesList() ); + } + } + return (values.size() == 0) ? null : (String[])values.toArray(new String[]{}); + } + + /** + *

Retrieves the array of values, if any, of an option.

+ * + * @param opt character name of the option + * @return Values of the argument if option is set, and has an argument, + * otherwise null. + */ + public String[] getOptionValues( char opt ) { + return getOptionValues( String.valueOf( opt ) ); + } + + /** + *

Retrieve the argument, if any, of an option.

+ * + * @param opt name of the option + * @param defaultValue is the default value to be returned if the option is not specified + * @return Value of the argument if option is set, and has an argument, + * otherwise defaultValue. + */ + public String getOptionValue( String opt, String defaultValue ) { + String answer = getOptionValue( opt ); + return ( answer != null ) ? answer : defaultValue; + } + + /** + *

Retrieve the argument, if any, of an option.

+ * + * @param opt character name of the option + * @param defaultValue is the default value to be returned if the option is not specified + * @return Value of the argument if option is set, and has an argument, + * otherwise defaultValue. + */ + public String getOptionValue( char opt, String defaultValue ) { + return getOptionValue( String.valueOf( opt ), defaultValue ); + } + + /** + *

Retrieve any left-over non-recognized options and arguments

+ * + * @return remaining items passed in but not parsed as an array + */ + public String[] getArgs() { + String[] answer = new String[ args.size() ]; + args.toArray( answer ); + return answer; + } + + /** + *

Retrieve any left-over non-recognized options and arguments

+ * + * @return remaining items passed in but not parsed as a List. + */ + public List getArgList() { + return args; + } + + /** + * jkeyes + * - commented out until it is implemented properly + *

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + /* + public String toString() { + StringBuffer buf = new StringBuffer(); + + buf.append( "[ CommandLine: [ options: " ); + buf.append( options.toString() ); + buf.append( " ] [ args: "); + buf.append( args.toString() ); + buf.append( " ] ]" ); + + return buf.toString(); + } + */ + + /** + *

Add left-over unrecognized option/argument.

+ * + * @param arg the unrecognised option/argument. + */ + void addArg(String arg) { + args.add( arg ); + } + + /** + *

Add an option to the command line. The values of + * the option are stored.

+ * + * @param opt the processed option + */ + void addOption( Option opt ) { + hashcodeMap.put( new Integer( opt.hashCode() ), opt ); + + String key = opt.getOpt(); + if( " ".equals(key) ) { + key = opt.getLongOpt(); + } + + if( options.get( key ) != null ) { + ((java.util.List)options.get( key )).add( opt ); + } + else { + options.put( key, new java.util.ArrayList() ); + ((java.util.List)options.get( key ) ).add( opt ); + } + } + + /** + *

Returns an iterator over the Option members of CommandLine.

+ * + * @return an Iterator over the processed {@link Option} + * members of this {@link CommandLine} + */ + public Iterator iterator( ) { + return hashcodeMap.values().iterator(); + } + + /** + *

Returns an array of the processed {@link Option}s.

+ * + * @return an array of the processed {@link Option}s. + */ + public Option[] getOptions( ) { + Collection processed = hashcodeMap.values(); + + // reinitialise array + optionsArray = new Option[ processed.size() ]; + + // return the array + return (Option[]) processed.toArray( optionsArray ); + } + +} diff --git a/installer/src/java/org/apache/commons/cli/CommandLineParser.java b/installer/src/java/org/apache/commons/cli/CommandLineParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/CommandLineParser.java @@ -0,0 +1,97 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + * A class that implements the CommandLineParser interface + * can parse a String array according to the {@link Options} specified + * and return a {@link CommandLine}. + * + * @author John Keyes (john at integralsource.com) + */ +public interface CommandLineParser { + + /** + * Parse the arguments according to the specified options. + * + * @param options the specified Options + * @param arguments the command line arguments + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered + * while parsing the command line tokens. + */ + public CommandLine parse( Options options, String[] arguments ) + throws ParseException; + + /** + * Parse the arguments according to the specified options. + * + * @param options the specified Options + * @param arguments the command line arguments + * @param stopAtNonOption specifies whether to continue parsing the + * arguments if a non option is encountered. + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered + * while parsing the command line tokens. + */ + public CommandLine parse( Options options, String[] arguments, boolean stopAtNonOption ) + throws ParseException; +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/GnuParser.java b/installer/src/java/org/apache/commons/cli/GnuParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/GnuParser.java @@ -0,0 +1,187 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * The class GnuParser provides an implementation of the + * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2662 $ + */ +public class GnuParser extends Parser { + + /** holder for flattened tokens */ + private ArrayList tokens = new ArrayList(); + + /** + *

Resets the members to their original state i.e. remove + * all of tokens entries. + */ + private void init() { + tokens.clear(); + } + + /** + *

This flatten method does so using the following rules: + *

    + *
  1. If an {@link Option} exists for the first character of + * the arguments entry AND an {@link Option} + * does not exist for the whole argument then + * add the first character as an option to the processed tokens + * list e.g. "-D" and add the rest of the entry to the also.
  2. + *
  3. Otherwise just add the token to the processed tokens list. + *
  4. + *
+ *

+ */ + protected String[] flatten( Options options, + String[] arguments, + boolean stopAtNonOption ) + { + init(); + boolean eatTheRest = false; + Option currentOption = null; + + for( int i = 0; i < arguments.length; i++ ) { + if( "--".equals( arguments[i] ) ) { + eatTheRest = true; + tokens.add( "--" ); + } + else if ( "-".equals( arguments[i] ) ) { + tokens.add( "-" ); + } + else if( arguments[i].startsWith( "-" ) ) { + Option option = options.getOption( arguments[i] ); + + // this is not an Option + if( option == null ) { + // handle special properties Option + Option specialOption = options.getOption( arguments[i].substring(0,2) ); + if( specialOption != null ) { + tokens.add( arguments[i].substring(0,2) ); + tokens.add( arguments[i].substring(2) ); + } + else if( stopAtNonOption ) { + eatTheRest = true; + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + else { + currentOption = option; + // special option + Option specialOption = options.getOption( arguments[i].substring(0,2) ); + if( specialOption != null && option == null ) { + tokens.add( arguments[i].substring(0,2) ); + tokens.add( arguments[i].substring(2) ); + } + else if( currentOption != null && currentOption.hasArg() ) { + if( currentOption.hasArg() ) { + tokens.add( arguments[i] ); + currentOption= null; + } + else if ( currentOption.hasArgs() ) { + tokens.add( arguments[i] ); + } + else if ( stopAtNonOption ) { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + else if (currentOption != null ) { + tokens.add( arguments[i] ); + } + else if ( stopAtNonOption ) { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + } + else { + tokens.add( arguments[i] ); + } + + if( eatTheRest ) { + for( i++; i < arguments.length; i++ ) { + tokens.add( arguments[i] ); + } + } + } + return (String[])tokens.toArray( new String[] {} ); + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/HelpFormatter.java b/installer/src/java/org/apache/commons/cli/HelpFormatter.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/HelpFormatter.java @@ -0,0 +1,542 @@ +/* + * $Header$ + * $Revision: 2690 $ + * $Date: 2006-03-25 23:39:57 -0800 (Sat, 25 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * A formatter of help messages for the current command line options + * + * @author Slawek Zachcial + * @author John Keyes (john at integralsource.com) + **/ +public class HelpFormatter +{ + // --------------------------------------------------------------- Constants + + public static final int DEFAULT_WIDTH = 74; + public static final int DEFAULT_LEFT_PAD = 1; + public static final int DEFAULT_DESC_PAD = 3; + public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; + public static final String DEFAULT_OPT_PREFIX = "-"; + public static final String DEFAULT_LONG_OPT_PREFIX = "--"; + public static final String DEFAULT_ARG_NAME = "arg"; + + // ------------------------------------------------------------------ Static + + // -------------------------------------------------------------- Attributes + + public int defaultWidth; + public int defaultLeftPad; + public int defaultDescPad; + public String defaultSyntaxPrefix; + public String defaultNewLine; + public String defaultOptPrefix; + public String defaultLongOptPrefix; + public String defaultArgName; + + // ------------------------------------------------------------ Constructors + public HelpFormatter() + { + defaultWidth = DEFAULT_WIDTH; + defaultLeftPad = DEFAULT_LEFT_PAD; + defaultDescPad = DEFAULT_DESC_PAD; + defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX; + defaultNewLine = System.getProperty("line.separator"); + defaultOptPrefix = DEFAULT_OPT_PREFIX; + defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; + defaultArgName = DEFAULT_ARG_NAME; + } + + // ------------------------------------------------------------------ Public + + public void printHelp( String cmdLineSyntax, + Options options ) + { + printHelp( defaultWidth, cmdLineSyntax, null, options, null, false ); + } + + public void printHelp( String cmdLineSyntax, + Options options, + boolean autoUsage ) + { + printHelp( defaultWidth, cmdLineSyntax, null, options, null, autoUsage ); + } + + public void printHelp( String cmdLineSyntax, + String header, + Options options, + String footer ) + { + printHelp( cmdLineSyntax, header, options, footer, false ); + } + + public void printHelp( String cmdLineSyntax, + String header, + Options options, + String footer, + boolean autoUsage ) + { + printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage ); + } + + public void printHelp( int width, + String cmdLineSyntax, + String header, + Options options, + String footer ) + { + printHelp( width, cmdLineSyntax, header, options, footer, false ); + } + + public void printHelp( int width, + String cmdLineSyntax, + String header, + Options options, + String footer, + boolean autoUsage ) + { + PrintWriter pw = new PrintWriter(System.out); + printHelp( pw, width, cmdLineSyntax, header, + options, defaultLeftPad, defaultDescPad, footer, autoUsage ); + pw.flush(); + } + public void printHelp( PrintWriter pw, + int width, + String cmdLineSyntax, + String header, + Options options, + int leftPad, + int descPad, + String footer ) + throws IllegalArgumentException + { + printHelp( pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false ); + } + + public void printHelp( PrintWriter pw, + int width, + String cmdLineSyntax, + String header, + Options options, + int leftPad, + int descPad, + String footer, + boolean autoUsage ) + throws IllegalArgumentException + { + if ( cmdLineSyntax == null || cmdLineSyntax.length() == 0 ) + { + throw new IllegalArgumentException("cmdLineSyntax not provided"); + } + + if ( autoUsage ) { + printUsage( pw, width, cmdLineSyntax, options ); + } + else { + printUsage( pw, width, cmdLineSyntax ); + } + + if ( header != null && header.trim().length() > 0 ) + { + printWrapped( pw, width, header ); + } + printOptions( pw, width, options, leftPad, descPad ); + if ( footer != null && footer.trim().length() > 0 ) + { + printWrapped( pw, width, footer ); + } + } + + /** + *

Prints the usage statement for the specified application.

+ * + * @param pw The PrintWriter to print the usage statement + * @param width ?? + * @param appName The application name + * @param options The command line Options + * + */ + public void printUsage( PrintWriter pw, int width, String app, Options options ) + { + // initialise the string buffer + StringBuffer buff = new StringBuffer( defaultSyntaxPrefix ).append( app ).append( " " ); + + // create a list for processed option groups + ArrayList list = new ArrayList(); + + // temp variable + Option option; + + // iterate over the options + for ( Iterator i = options.getOptions().iterator(); i.hasNext(); ) + { + // get the next Option + option = (Option) i.next(); + + // check if the option is part of an OptionGroup + OptionGroup group = options.getOptionGroup( option ); + + // if the option is part of a group + if( group != null ) { + // if the group has not already been processed + if(!list.contains(group)) { + + // add the group to the processed list + list.add( group ); + + // get the names of the options from the OptionGroup + Collection names = group.getNames(); + + buff.append( "[" ); + + // for each option in the OptionGroup + for( Iterator iter = names.iterator(); iter.hasNext(); ) { + buff.append( iter.next() ); + if( iter.hasNext() ) { + buff.append( " | " ); + } + } + buff.append( "] " ); + } + } else { // if the Option is not part of an OptionGroup + // if the Option is not a required option + if( !option.isRequired() ) { + buff.append( "[" ); + } + + if( !" ".equals( option.getOpt() ) ) { + buff.append( "-" ).append( option.getOpt() ); + } + else { + buff.append( "--" ).append( option.getLongOpt() ); + } + + if( option.hasArg() ){ + buff.append( " " ); + } + + // if the Option has a value + if( option.hasArg() ) { + buff.append( option.getArgName() ); + } + + // if the Option is not a required option + if( !option.isRequired() ) { + buff.append( "]" ); + } + buff.append( " " ); + } + } + + // call printWrapped + printWrapped( pw, width, buff.toString().indexOf(' ')+1, + buff.toString() ); + } + + public void printUsage( PrintWriter pw, int width, String cmdLineSyntax ) + { + int argPos = cmdLineSyntax.indexOf(' ') + 1; + printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, + defaultSyntaxPrefix + cmdLineSyntax); + } + + public void printOptions( PrintWriter pw, int width, Options options, int leftPad, int descPad ) + { + StringBuffer sb = new StringBuffer(); + renderOptions(sb, width, options, leftPad, descPad); + pw.println(sb.toString()); + } + + public void printWrapped( PrintWriter pw, int width, String text ) + { + printWrapped(pw, width, 0, text); + } + + public void printWrapped( PrintWriter pw, int width, int nextLineTabStop, String text ) + { + StringBuffer sb = new StringBuffer(text.length()); + renderWrappedText(sb, width, nextLineTabStop, text); + pw.println(sb.toString()); + } + + // --------------------------------------------------------------- Protected + + protected StringBuffer renderOptions( StringBuffer sb, + int width, + Options options, + int leftPad, + int descPad ) + { + final String lpad = createPadding(leftPad); + final String dpad = createPadding(descPad); + + //first create list containing only -a,--aaa where -a is opt and --aaa is + //long opt; in parallel look for the longest opt string + //this list will be then used to sort options ascending + int max = 0; + StringBuffer optBuf; + List prefixList = new ArrayList(); + Option option; + List optList = options.helpOptions(); + if (!options.isSortAsAdded()) { + Collections.sort(optList, new StringBufferComparator()); + } + for ( Iterator i = optList.iterator(); i.hasNext(); ) + { + option = (Option) i.next(); + optBuf = new StringBuffer(8); + + if (option.getOpt().equals(" ")) + { + optBuf.append(lpad).append(" " + defaultLongOptPrefix).append(option.getLongOpt()); + } + else + { + optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt()); + if ( option.hasLongOpt() ) + { + optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt()); + } + + } + + if( option.hasArg() ) { + if( option.hasArgName() ) { + optBuf.append(" <").append( option.getArgName() ).append( '>' ); + } + else { + optBuf.append(' '); + } + } + + prefixList.add(optBuf); + max = optBuf.length() > max ? optBuf.length() : max; + } + int x = 0; + for ( Iterator i = optList.iterator(); i.hasNext(); ) + { + option = (Option) i.next(); + optBuf = new StringBuffer( prefixList.get( x++ ).toString() ); + + if ( optBuf.length() < max ) + { + optBuf.append(createPadding(max - optBuf.length())); + } + optBuf.append( dpad ); + + int nextLineTabStop = max + descPad; + renderWrappedText(sb, width, nextLineTabStop, + optBuf.append(option.getDescription()).toString()); + if ( i.hasNext() ) + { + sb.append(defaultNewLine); + } + } + + return sb; + } + + protected StringBuffer renderWrappedText( StringBuffer sb, + int width, + int nextLineTabStop, + String text ) + { + int pos = findWrapPos( text, width, 0); + if ( pos == -1 ) + { + sb.append(rtrim(text)); + return sb; + } + else + { + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + //all following lines must be padded with nextLineTabStop space characters + final String padding = createPadding(nextLineTabStop); + + while ( true ) + { + text = padding + text.substring(pos).trim(); + pos = findWrapPos( text, width, nextLineTabStop ); + if ( pos == -1 ) + { + sb.append(text); + return sb; + } + + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + } + + /** + * Finds the next text wrap position after startPos for the text + * in sb with the column width width. + * The wrap point is the last postion before startPos+width having a whitespace + * character (space, \n, \r). + * + * @param sb text to be analyzed + * @param width width of the wrapped text + * @param startPos position from which to start the lookup whitespace character + * @return postion on which the text must be wrapped or -1 if the wrap position is at the end + * of the text + */ + protected int findWrapPos( String text, int width, int startPos ) + { + int pos = -1; + // the line ends before the max wrap pos or a new line char found + if ( ((pos = text.indexOf('\n', startPos)) != -1 && pos <= width) || + ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width) ) + { + return pos; + } + else if ( (startPos + width) >= text.length() ) + { + return -1; + } + + //look for the last whitespace character before startPos+width + pos = startPos + width; + char c; + while ( pos >= startPos && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) + { + --pos; + } + //if we found it - just return + if ( pos > startPos ) + { + return pos; + } + else + { + //must look for the first whitespace chearacter after startPos + width + pos = startPos + width; + while ( pos <= text.length() && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) + { + ++pos; + } + return pos == text.length() ? -1 : pos; + } + } + + protected String createPadding(int len) + { + StringBuffer sb = new StringBuffer(len); + for ( int i = 0; i < len; ++i ) + { + sb.append(' '); + } + return sb.toString(); + } + + protected String rtrim( String s ) + { + if ( s == null || s.length() == 0 ) + { + return s; + } + + int pos = s.length(); + while ( pos >= 0 && Character.isWhitespace(s.charAt(pos-1)) ) + { + --pos; + } + return s.substring(0, pos); + } + + // ------------------------------------------------------- Package protected + + // ----------------------------------------------------------------- Private + + // ----------------------------------------------------------- Inner classes + + private static class StringBufferComparator + implements Comparator + { + public int compare( Object o1, Object o2 ) + { + String str1 = stripPrefix(o1.toString()); + String str2 = stripPrefix(o2.toString()); + return (str1.compareTo(str2)); + } + + private String stripPrefix(String strOption) + { + // Strip any leading '-' characters + int iStartIndex = strOption.lastIndexOf('-'); + if (iStartIndex == -1) + { + iStartIndex = 0; + } + return strOption.substring(iStartIndex); + + } + } +} diff --git a/installer/src/java/org/apache/commons/cli/MissingArgumentException.java b/installer/src/java/org/apache/commons/cli/MissingArgumentException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/MissingArgumentException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Thrown when an option requiring an argument + * is not provided with an argument.

+ * + * @author John Keyes (john at integralsource.com) + * @see ParseException + */ +public class MissingArgumentException extends ParseException { + + /** + *

Construct a new MissingArgumentException + * with the specified detail message.

+ * + * @param message the detail message + */ + public MissingArgumentException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/MissingOptionException.java b/installer/src/java/org/apache/commons/cli/MissingOptionException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/MissingOptionException.java @@ -0,0 +1,81 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Thrown when a required option has not been provided.

+ * + * @author John Keyes ( john at integralsource.com ) + * @see ParseException + */ +public class MissingOptionException extends ParseException { + + /** + *

Construct a new MissingSelectedException + * with the specified detail message.

+ * + * @param message the detail message + */ + public MissingOptionException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Option.java b/installer/src/java/org/apache/commons/cli/Option.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Option.java @@ -0,0 +1,575 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: Option.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; + +/**

Describes a single command-line option. It maintains + * information regarding the short-name of the option, the long-name, + * if any exists, a flag indicating if an argument is required for + * this option, and a self-documenting description of the option.

+ * + *

An Option is not created independantly, but is create through + * an instance of {@link Options}.

+ * + * @see org.apache.commons.cli.Options + * @see org.apache.commons.cli.CommandLine + * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @version $Revision: 2662 $ + */ + +public class Option implements Cloneable { + + /** constant that specifies the number of argument values has not been specified */ + public final static int UNINITIALIZED = -1; + + /** constant that specifies the number of argument values is infinite */ + public final static int UNLIMITED_VALUES = -2; + + /** opt the single character representation of the option */ + private String opt; + + /** longOpt is the long representation of the option */ + private String longOpt; + + /** hasArg specifies whether this option has an associated argument */ + private boolean hasArg; + + /** argName specifies the name of the argument for this option */ + private String argName; + + /** description of the option */ + private String description; + + /** required specifies whether this option is required to be present */ + private boolean required; + + /** specifies whether the argument value of this Option is optional */ + private boolean optionalArg; + + /** + * numberOfArgs specifies the number of argument values this option + * can have + */ + private int numberOfArgs = UNINITIALIZED; + + /** the type of this Option */ + private Object type; + + /** the list of argument values **/ + private ArrayList values = new ArrayList(); + + /** option char (only valid for single character options) */ + private char id; + + /** the character that is the value separator */ + private char valuesep; + + /** + *

Validates whether opt is a permissable Option + * shortOpt. The rules that specify if the opt + * is valid are:

+ *
    + *
  • opt is not NULL
  • + *
  • a single character opt that is either + * ' '(special case), '?', '@' or a letter
  • + *
  • a multi character opt that only contains + * letters.
  • + *
+ * + * @param opt The option string to validate + * @throws IllegalArgumentException if the Option is not valid. + */ + private void validateOption( String opt ) + throws IllegalArgumentException + { + // check that opt is not NULL + if( opt == null ) { + throw new IllegalArgumentException( "opt is null" ); + } + // handle the single character opt + else if( opt.length() == 1 ) { + char ch = opt.charAt( 0 ); + if ( !isValidOpt( ch ) ) { + throw new IllegalArgumentException( "illegal option value '" + + ch + "'" ); + } + id = ch; + } + // handle the multi character opt + else { + char[] chars = opt.toCharArray(); + for( int i = 0; i < chars.length; i++ ) { + if( !isValidChar( chars[i] ) ) { + throw new IllegalArgumentException( "opt contains illegal character value '" + chars[i] + "'" ); + } + } + } + } + + /** + *

Returns whether the specified character is a valid Option.

+ * + * @param c the option to validate + * @return true if c is a letter, ' ', '?' or '@', otherwise false. + */ + private boolean isValidOpt( char c ) { + return ( isValidChar( c ) || c == ' ' || c == '?' || c == '@' ); + } + + /** + *

Returns whether the specified character is a valid character.

+ * + * @param c the character to validate + * @return true if c is a letter. + */ + private boolean isValidChar( char c ) { + return Character.isJavaIdentifierPart( c ); + } + + /** + *

Returns the id of this Option. This is only set when the + * Option shortOpt is a single character. This is used for switch + * statements.

+ * + * @return the id of this Option + */ + public int getId( ) { + return id; + } + + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, String description ) + throws IllegalArgumentException + { + this( opt, null, false, description ); + } + + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, boolean hasArg, String description ) + throws IllegalArgumentException + { + this( opt, null, hasArg, description ); + } + + /** + *

Creates an Option using the specified parameters.

+ * + * @param opt short representation of the option + * @param longOpt the long representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, String longOpt, boolean hasArg, String description ) + throws IllegalArgumentException + { + // ensure that the option is valid + validateOption( opt ); + + this.opt = opt; + this.longOpt = longOpt; + + // if hasArg is set then the number of arguments is 1 + if( hasArg ) { + this.numberOfArgs = 1; + } + + this.hasArg = hasArg; + this.description = description; + } + + /**

Retrieve the name of this Option.

+ * + *

It is this String which can be used with + * {@link CommandLine#hasOption(String opt)} and + * {@link CommandLine#getOptionValue(String opt)} to check + * for existence and argument.

+ * + * @return The name of this option + */ + public String getOpt() { + return this.opt; + } + + /** + *

Retrieve the type of this Option.

+ * + * @return The type of this option + */ + public Object getType() { + return this.type; + } + + /** + *

Sets the type of this Option.

+ * + * @param type the type of this Option + */ + public void setType( Object type ) { + this.type = type; + } + + /** + *

Retrieve the long name of this Option.

+ * + * @return Long name of this option, or null, if there is no long name + */ + public String getLongOpt() { + return this.longOpt; + } + + /** + *

Sets the long name of this Option.

+ * + * @param longOpt the long name of this Option + */ + public void setLongOpt( String longOpt ) { + this.longOpt = longOpt; + } + + /** + *

Sets whether this Option can have an optional argument.

+ * + * @param optionalArg specifies whether the Option can have + * an optional argument. + */ + public void setOptionalArg( boolean optionalArg ) { + this.optionalArg = optionalArg; + } + + /** + * @return whether this Option can have an optional argument + */ + public boolean hasOptionalArg( ) { + return this.optionalArg; + } + + /**

Query to see if this Option has a long name

+ * + * @return boolean flag indicating existence of a long name + */ + public boolean hasLongOpt() { + return ( this.longOpt != null ); + } + + /**

Query to see if this Option requires an argument

+ * + * @return boolean flag indicating if an argument is required + */ + public boolean hasArg() { + return this.numberOfArgs > 0 || numberOfArgs == UNLIMITED_VALUES; + } + + /**

Retrieve the self-documenting description of this Option

+ * + * @return The string description of this option + */ + public String getDescription() { + return this.description; + } + + /** + *

Query to see if this Option requires an argument

+ * + * @return boolean flag indicating if an argument is required + */ + public boolean isRequired() { + return this.required; + } + + /** + *

Sets whether this Option is mandatory.

+ * + * @param required specifies whether this Option is mandatory + */ + public void setRequired( boolean required ) { + this.required = required; + } + + /** + *

Sets the display name for the argument value.

+ * + * @param argName the display name for the argument value. + */ + public void setArgName( String argName ) { + this.argName = argName; + } + + /** + *

Gets the display name for the argument value.

+ * + * @return the display name for the argument value. + */ + public String getArgName() { + return this.argName; + } + + /** + *

Returns whether the display name for the argument value + * has been set.

+ * + * @return if the display name for the argument value has been + * set. + */ + public boolean hasArgName() { + return (this.argName != null && this.argName.length() > 0 ); + } + + /** + *

Query to see if this Option can take many values

+ * + * @return boolean flag indicating if multiple values are allowed + */ + public boolean hasArgs() { + return ( this.numberOfArgs > 1 || this.numberOfArgs == UNLIMITED_VALUES ); + } + + /** + *

Sets the number of argument values this Option can take.

+ * + * @param num the number of argument values + */ + public void setArgs( int num ) { + this.numberOfArgs = num; + } + + /** + *

Sets the value separator. For example if the argument value + * was a Java property, the value separator would be '='.

+ * + * @param sep The value separator. + */ + public void setValueSeparator( char sep ) { + this.valuesep = sep; + } + + /** + *

Returns the value separator character.

+ * + * @return the value separator character. + */ + public char getValueSeparator() { + return this.valuesep; + } + + /** + *

Returns the number of argument values this Option can take.

+ * + * @return num the number of argument values + */ + public int getArgs( ) { + return this.numberOfArgs; + } + + /** + *

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + public String toString() { + StringBuffer buf = new StringBuffer().append("[ option: "); + + buf.append( this.opt ); + + if ( this.longOpt != null ) { + buf.append(" ") + .append(this.longOpt); + } + + buf.append(" "); + + if ( hasArg ) { + buf.append( "+ARG" ); + } + + buf.append(" :: ") + .append( this.description ); + + if ( this.type != null ) { + buf.append(" :: ") + .append( this.type ); + } + + buf.append(" ]"); + return buf.toString(); + } + + /** + *

Adds the specified value to this Option.

+ * + * @param value is a/the value of this Option + */ + public boolean addValue( String value ) { + + switch( numberOfArgs ) { + case UNINITIALIZED: + return false; + case UNLIMITED_VALUES: + if( getValueSeparator() > 0 ) { + int index = 0; + while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { + this.values.add( value.substring( 0, index ) ); + value = value.substring( index+1 ); + } + } + this.values.add( value ); + return true; + default: + if( getValueSeparator() > 0 ) { + int index = 0; + while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { + if( values.size() > numberOfArgs-1 ) { + return false; + } + this.values.add( value.substring( 0, index ) ); + value = value.substring( index+1 ); + } + } + if( values.size() > numberOfArgs-1 ) { + return false; + } + this.values.add( value ); + return true; + } + } + + /** + * @return the value/first value of this Option or + * null if there are no values. + */ + public String getValue() { + return this.values.size()==0 ? null : (String)this.values.get( 0 ); + } + + /** + * @return the specified value of this Option or + * null if there are no values. + */ + public String getValue( int index ) + throws IndexOutOfBoundsException + { + return ( this.values.size()==0 ) ? null : (String)this.values.get( index ); + } + + /** + * @return the value/first value of this Option or the + * defaultValue if there are no values. + */ + public String getValue( String defaultValue ) { + String value = getValue( ); + return ( value != null ) ? value : defaultValue; + } + + /** + * @return the values of this Option as a String array + * or null if there are no values + */ + public String[] getValues() { + return this.values.size()==0 ? null : (String[])this.values.toArray(new String[]{}); + } + + /** + * @return the values of this Option as a List + * or null if there are no values + */ + public java.util.List getValuesList() { + return this.values; + } + + /** + * @return a copy of this Option + */ + public Object clone() { + Option option = new Option( getOpt(), getDescription() ); + option.setArgs( getArgs() ); + option.setOptionalArg( hasOptionalArg() ); + option.setRequired( isRequired() ); + option.setLongOpt( getLongOpt() ); + option.setType( getType() ); + option.setValueSeparator( getValueSeparator() ); + return option; + } +} diff --git a/installer/src/java/org/apache/commons/cli/OptionBuilder.java b/installer/src/java/org/apache/commons/cli/OptionBuilder.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/OptionBuilder.java @@ -0,0 +1,368 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

OptionBuilder allows the user to create Options using descriptive + * methods.

+ *

Details on the Builder pattern can be found at + * http://c2.com/cgi-bin/wiki?BuilderPattern.

+ * + * @author John Keyes ( john at integralsource.com ) + * @since 1.0 + */ +public class OptionBuilder { + + /** long option */ + private static String longopt; + /** option description */ + private static String description; + /** argument name */ + private static String argName; + /** is required? */ + private static boolean required; + /** the number of arguments */ + private static int numberOfArgs = Option.UNINITIALIZED; + /** option type */ + private static Object type; + /** option can have an optional argument value */ + private static boolean optionalArg; + /** value separator for argument value */ + private static char valuesep; + + /** option builder instance */ + private static OptionBuilder instance = new OptionBuilder(); + + // private constructor + private OptionBuilder() { + } + + /** + *

Resets the member variables to their default values.

+ */ + private static void reset() { + description = null; + argName = null; + longopt = null; + type = null; + required = false; + numberOfArgs = Option.UNINITIALIZED; + + // PMM 9/6/02 - these were missing + optionalArg = false; + valuesep = (char) 0; + } + + /** + *

The next Option created will have the following long option value.

+ * + * @param longopt the long option value + * @return the OptionBuilder instance + */ + public static OptionBuilder withLongOpt( String longopt ) { + instance.longopt = longopt; + return instance; + } + + /** + *

The next Option created will require an argument value.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArg( ) { + instance.numberOfArgs = 1; + return instance; + } + + /** + *

The next Option created will require an argument value if + * hasArg is true.

+ * + * @param hasArg if true then the Option has an argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArg( boolean hasArg ) { + instance.numberOfArgs = ( hasArg == true ) ? 1 : Option.UNINITIALIZED; + return instance; + } + + /** + *

The next Option created will have the specified argument value + * name.

+ * + * @param name the name for the argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder withArgName( String name ) { + instance.argName = name; + return instance; + } + + /** + *

The next Option created will be required.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder isRequired( ) { + instance.required = true; + return instance; + } + + /** + *

The next Option created uses sep as a means to + * separate argument values.

+ * + * Example: + *
+     * Option opt = OptionBuilder.withValueSeparator( ':' )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * 
+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder withValueSeparator( char sep ) { + instance.valuesep = sep; + return instance; + } + + /** + *

The next Option created uses '=' as a means to + * separate argument values.

+ * + * Example: + *
+     * Option opt = OptionBuilder.withValueSeparator( )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * 
+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder withValueSeparator( ) { + instance.valuesep = '='; + return instance; + } + + /** + *

The next Option created will be required if required + * is true.

+ * + * @param required if true then the Option is required + * @return the OptionBuilder instance + */ + public static OptionBuilder isRequired( boolean required ) { + instance.required = required; + return instance; + } + + /** + *

The next Option created can have unlimited argument values.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArgs( ) { + instance.numberOfArgs = Option.UNLIMITED_VALUES; + return instance; + } + + /** + *

The next Option created can have num + * argument values.

+ * + * @param num the number of args that the option can have + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArgs( int num ) { + instance.numberOfArgs = num; + return instance; + } + + /** + *

The next Option can have an optional argument.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArg( ) { + instance.numberOfArgs = 1; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option can have an unlimited number of + * optional arguments.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArgs( ) { + instance.numberOfArgs = Option.UNLIMITED_VALUES; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option can have the specified number of + * optional arguments.

+ * + * @param numArgs - the maximum number of optional arguments + * the next Option created can have. + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArgs( int numArgs ) { + instance.numberOfArgs = numArgs; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option created will have a value that will be an instance + * of type.

+ * + * @param type the type of the Options argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder withType( Object type ) { + instance.type = type; + return instance; + } + + /** + *

The next Option created will have the specified description

+ * + * @param description a description of the Option's purpose + * @return the OptionBuilder instance + */ + public static OptionBuilder withDescription( String description ) { + instance.description = description; + return instance; + } + + /** + *

Create an Option using the current settings and with + * the specified Option char.

+ * + * @param opt the character representation of the Option + * @return the Option instance + * @throws IllegalArgumentException if opt is not + * a valid character. See Option. + */ + public static Option create( char opt ) + throws IllegalArgumentException + { + return create( String.valueOf( opt ) ); + } + + /** + *

Create an Option using the current settings

+ * + * @return the Option instance + * @throws IllegalArgumentException if longOpt has + * not been set. + */ + public static Option create() + throws IllegalArgumentException + { + if( longopt == null ) { + throw new IllegalArgumentException( "must specify longopt" ); + } + + return create( " " ); + } + + /** + *

Create an Option using the current settings and with + * the specified Option char.

+ * + * @param opt the java.lang.String representation + * of the Option + * @return the Option instance + * @throws IllegalArgumentException if opt is not + * a valid character. See Option. + */ + public static Option create( String opt ) + throws IllegalArgumentException + { + // create the option + Option option = new Option( opt, description ); + + // set the option properties + option.setLongOpt( longopt ); + option.setRequired( required ); + option.setOptionalArg( optionalArg ); + option.setArgs( numberOfArgs ); + option.setType( type ); + option.setValueSeparator( valuesep ); + option.setArgName( argName ); + // reset the OptionBuilder properties + instance.reset(); + + // return the Option instance + return option; + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/OptionGroup.java b/installer/src/java/org/apache/commons/cli/OptionGroup.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/OptionGroup.java @@ -0,0 +1,187 @@ +/* + * $Header$ + * $Revision: 2690 $ + * $Date: 2006-03-25 23:39:57 -0800 (Sat, 25 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * A group of mutually exclusive options. + * @author John Keyes ( john at integralsource.com ) + * @version $Revision: 2690 $ + */ +public class OptionGroup { + + /** hold the options in added order */ + private List optionList = new ArrayList(); + + /** the name of the selected option */ + private String selected; + + /** specified whether this group is required */ + private boolean required; + + /** + * add opt to this group + * + * @param opt the option to add to this group + * @return this option group with opt added + */ + public OptionGroup addOption(Option opt) { + optionList.add(opt); + return this; + } + + /** + * @return the names of the options in this group as a + * Collection + */ + public Collection getNames() { + List namesList = new ArrayList(); + Iterator optionsIterator = optionList.iterator(); + while (optionsIterator.hasNext()) { + Option option = (Option) optionsIterator.next(); + namesList.add("-" + option.getOpt()); + } + return namesList; + } + + /** + * @return the options in this group as a Collection + */ + public Collection getOptions() { + return optionList; + } + + /** + * set the selected option of this group to name. + * @param opt the option that is selected + * @throws AlreadySelectedException if an option from this group has + * already been selected. + */ + public void setSelected(Option opt) throws AlreadySelectedException { + // if no option has already been selected or the + // same option is being reselected then set the + // selected member variable + + if ( this.selected == null || this.selected.equals( opt.getOpt() ) ) { + this.selected = opt.getOpt(); + } + else { + throw new AlreadySelectedException( "an option from this group has " + + "already been selected: '" + + selected + "'"); + } + } + + /** + * @return the selected option name + */ + public String getSelected() { + return selected; + } + + /** + * @param required specifies if this group is required + */ + public void setRequired( boolean required ) { + this.required = required; + } + + /** + * Returns whether this option group is required. + * + * @returns whether this option group is required + */ + public boolean isRequired() { + return this.required; + } + + /** + *

Returns the stringified version of this OptionGroup.

+ * @return the stringified representation of this group + */ + public String toString() { + StringBuffer buff = new StringBuffer(); + + Iterator iter = getOptions().iterator(); + + buff.append( "[" ); + while( iter.hasNext() ) { + Option option = (Option)iter.next(); + + buff.append( "-" ); + buff.append( option.getOpt() ); + buff.append( " " ); + buff.append( option.getDescription( ) ); + + if( iter.hasNext() ) { + buff.append( ", " ); + } + } + buff.append( "]" ); + + return buff.toString(); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Options.java b/installer/src/java/org/apache/commons/cli/Options.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Options.java @@ -0,0 +1,331 @@ +/* + * $Header$ + * $Revision: 2694 $ + * $Date: 2006-03-27 11:45:13 -0800 (Mon, 27 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/**

Main entry-point into the library.

+ * + *

Options represents a collection of {@link Option} objects, which + * describe the possible options for a command-line.

+ * + *

It may flexibly parse long and short options, with or without + * values. Additionally, it may parse only a portion of a commandline, + * allowing for flexible multi-stage parsing.

+ * + * @see org.apache.commons.cli.CommandLine + * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @version $Revision: 2694 $ + */ +public class Options { + + /** a map of the options with the character key */ + private Map shortOpts = new HashMap(); + + /** a map of the options with the long key */ + private Map longOpts = new HashMap(); + + /** a list of the required options */ + private List requiredOpts = new ArrayList(); + + /** a map of the option groups */ + private Map optionGroups = new HashMap(); + + /** a list of all options, in sequence of addition, only in use when isSortAsAdded() */ + private List _addedOpts = new ArrayList(); + + /** flag for the sort behaviour */ + private boolean _sortAsAdded; + + /** + * Construct a new Options descriptor + */ + public Options() { + setSortAsAdded(false); + } + + /** + * Set the sort sequence: true means in sequence of addition, false will use the + * default (undefined) sorting + */ + public void setSortAsAdded(boolean sortAsAdded) { + _sortAsAdded = sortAsAdded; + } + + /** + *

Add the specified option group.

+ * + * @param group the OptionGroup that is to be added + * @return the resulting Options instance + */ + public Options addOptionGroup( OptionGroup group ) { + Iterator options = group.getOptions().iterator(); + + if( group.isRequired() ) { + requiredOpts.add( group ); + } + + while( options.hasNext() ) { + Option option = (Option)options.next(); + // an Option cannot be required if it is in an + // OptionGroup, either the group is required or + // nothing is required + option.setRequired( false ); + addOption( option ); + + optionGroups.put( option.getOpt(), group ); + } + + return this; + } + + /**

Add an option that only contains a short-name

+ *

It may be specified as requiring an argument.

+ * + * @param opt Short single-character name of the option. + * @param hasArg flag signally if an argument is required after this option + * @param description Self-documenting description + * @return the resulting Options instance + */ + public Options addOption(String opt, boolean hasArg, String description) { + addOption( opt, null, hasArg, description ); + return this; + } + + /**

Add an option that contains a short-name and a long-name

+ *

It may be specified as requiring an argument.

+ * + * @param opt Short single-character name of the option. + * @param longOpt Long multi-character name of the option. + * @param hasArg flag signally if an argument is required after this option + * @param description Self-documenting description + * @return the resulting Options instance + */ + public Options addOption(String opt, String longOpt, boolean hasArg, String description) { + addOption( new Option( opt, longOpt, hasArg, description ) ); + return this; + } + + /** + *

Adds an option instance

+ * + * @param opt the option that is to be added + * @return the resulting Options instance + */ + public Options addOption(Option opt) { + String shortOpt = "-" + opt.getOpt(); + + // add it to the long option list + if ( opt.hasLongOpt() ) { + longOpts.put( "--" + opt.getLongOpt(), opt ); + } + + // if the option is required add it to the required list + if ( opt.isRequired() ) { + requiredOpts.add( shortOpt ); + } + + shortOpts.put( shortOpt, opt ); + + if (isSortAsAdded()) { + _addedOpts.add(opt); + } + + return this; + } + + /**

Retrieve a read-only list of options in this set

+ * + * @return read-only Collection of {@link Option} objects in this descriptor + */ + public Collection getOptions() { + if (isSortAsAdded()) { + return _addedOpts; + } else { + List opts = new ArrayList(shortOpts.values()); + + // now look through the long opts to see if there are any Long-opt + // only options + Iterator iter = longOpts.values().iterator(); + while (iter.hasNext()) { + Object item = iter.next(); + if (!opts.contains(item)) { + opts.add(item); + } + } + return Collections.unmodifiableCollection(opts); + } + } + + /** + * @return true if options should be sorted in sequence of addition. + */ + public boolean isSortAsAdded() { + return _sortAsAdded; + } + + /** + *

Returns the Options for use by the HelpFormatter.

+ * + * @return the List of Options + */ + List helpOptions() { + if (isSortAsAdded()) { + return _addedOpts; + } else { + return new ArrayList(shortOpts.values()); + } + } + + /**

Returns the required options as a + * java.util.Collection.

+ * + * @return Collection of required options + */ + public List getRequiredOptions() { + return requiredOpts; + } + + /**

Retrieve the named {@link Option}

+ * + * @param opt short or long name of the {@link Option} + * @return the option represented by opt + */ + public Option getOption( String opt ) { + + Option option = null; + + // short option + if( opt.length() == 1 ) { + option = (Option)shortOpts.get( "-" + opt ); + } + // long option + else if( opt.startsWith( "--" ) ) { + option = (Option)longOpts.get( opt ); + } + // a just-in-case + else { + option = (Option)shortOpts.get( opt ); + } + + return (option == null) ? null : (Option)option.clone(); + } + + /** + *

Returns whether the named {@link Option} is a member + * of this {@link Options}

+ * + * @param opt short or long name of the {@link Option} + * @return true if the named {@link Option} is a member + * of this {@link Options} + */ + public boolean hasOption( String opt ) { + + // short option + if( opt.length() == 1 ) { + return shortOpts.containsKey( "-" + opt ); + } + // long option + else if( opt.startsWith( "--" ) ) { + return longOpts.containsKey( opt ); + } + // a just-in-case + else { + return shortOpts.containsKey( opt ); + } + } + + /**

Returns the OptionGroup the opt + * belongs to.

+ * @param opt the option whose OptionGroup is being queried. + * + * @return the OptionGroup if opt is part + * of an OptionGroup, otherwise return null + */ + public OptionGroup getOptionGroup( Option opt ) { + return (OptionGroup)optionGroups.get( opt.getOpt() ); + } + + /**

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + public String toString() { + StringBuffer buf = new StringBuffer(); + + buf.append("[ Options: [ short "); + buf.append( shortOpts.toString() ); + buf.append( " ] [ long " ); + buf.append( longOpts ); + buf.append( " ]"); + + return buf.toString(); + } +} diff --git a/installer/src/java/org/apache/commons/cli/ParseException.java b/installer/src/java/org/apache/commons/cli/ParseException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/ParseException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Base for Exceptions thrown during parsing of a command-line.

+ * + * @author bob mcwhirter (bob @ werken.com) + * @version $Revision: 2662 $ + */ +public class ParseException extends Exception +{ + + /** + *

Construct a new ParseException + * with the specified detail message.

+ * + * @param message the detail message + */ + public ParseException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Parser.java b/installer/src/java/org/apache/commons/cli/Parser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Parser.java @@ -0,0 +1,282 @@ +/* + * $Header$ + * $Revision: 2665 $ + * $Date: 2006-02-18 14:24:36 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + *

Parser creates {@link CommandLine}s.

+ * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2665 $ + */ +public abstract class Parser implements CommandLineParser { + + /** commandline instance */ + private CommandLine cmd; + /** current Options */ + private Options options; + /** list of required options strings */ + private List requiredOptions; + + /** + *

Subclasses must implement this method to reduce + * the arguments that have been passed to the parse + * method.

+ * + * @param opts The Options to parse the arguments by. + * @param args The arguments that have to be flattened. + * @param stopAtNonOption specifies whether to stop + * flattening when a non option has been encountered + * @return a String array of the flattened arguments + */ + abstract protected String[] flatten( Options opts, + String[] arguments, + boolean stopAtNonOption ); + + /** + *

Parses the specified arguments + * based on the specifed {@link Options}.

+ * + * @param options the Options + * @param arguments the arguments + * @return the CommandLine + * @throws ParseException if an error occurs when parsing the + * arguments. + */ + public CommandLine parse( Options options, String[] arguments ) + throws ParseException + { + return parse( options, arguments, false ); + } + + /** + *

Parses the specified arguments + * based on the specifed {@link Options}.

+ * + * @param options the Options + * @param arguments the arguments + * @param stopAtNonOption specifies whether to stop + * interpreting the arguments when a non option has + * been encountered and to add them to the CommandLines + * args list. + * @return the CommandLine + * @throws ParseException if an error occurs when parsing the + * arguments. + */ + public CommandLine parse( Options opts, + String[] arguments, + boolean stopAtNonOption ) + throws ParseException + { + // initialise members + options = opts; + requiredOptions = options.getRequiredOptions(); + cmd = new CommandLine(); + + boolean eatTheRest = false; + + List tokenList = Arrays.asList( flatten( opts, arguments, stopAtNonOption ) ); + ListIterator iterator = tokenList.listIterator(); + + // process each flattened token + while( iterator.hasNext() ) { + String t = (String)iterator.next(); + + // the value is the double-dash + if( "--".equals( t ) ) { + eatTheRest = true; + } + // the value is a single dash + else if( "-".equals( t ) ) { + if( stopAtNonOption ) { + eatTheRest = true; + } + else { + cmd.addArg(t ); + } + } + // the value is an option + else if( t.startsWith( "-" ) ) { + if ( stopAtNonOption && !options.hasOption( t ) ) { + eatTheRest = true; + cmd.addArg( t ); + } + else { + processOption( t, iterator ); + } + } + // the value is an argument + else { + cmd.addArg( t ); + if( stopAtNonOption ) { + eatTheRest = true; + } + } + + // eat the remaining tokens + if( eatTheRest ) { + while( iterator.hasNext() ) { + String str = (String)iterator.next(); + // ensure only one double-dash is added + if( !"--".equals( str ) ) { + cmd.addArg( str ); + } + } + } + } + checkRequiredOptions(); + return cmd; + } + + /** + *

Throws a {@link MissingOptionException} if all of the + * required options are no present.

+ */ + private void checkRequiredOptions() + throws MissingOptionException + { + + // if there are required options that have not been + // processsed + if( requiredOptions.size() > 0 ) { + Iterator iter = requiredOptions.iterator(); + StringBuffer buff = new StringBuffer(); + + // loop through the required options + while( iter.hasNext() ) { + buff.append( iter.next() ); + } + + throw new MissingOptionException( buff.toString() ); + } + } + + public void processArgs( Option opt, ListIterator iter ) + throws ParseException + { + // loop until an option is found + while( iter.hasNext() ) { + String var = (String)iter.next(); + + // found an Option + if( options.hasOption( var ) ) { + iter.previous(); + break; + } + // found a value + else if( !opt.addValue( var ) ) { + iter.previous(); + break; + } + } + + if( opt.getValues() == null && !opt.hasOptionalArg() ) { + throw new MissingArgumentException( "no argument for option: " + opt.getOpt() + " / " + opt.getLongOpt() ); + } + } + + private void processOption( String arg, ListIterator iter ) + throws ParseException + { + // get the option represented by arg + Option opt = null; + + boolean hasOption = options.hasOption( arg ); + + // if there is no option throw an UnrecognisedOptionException + if( !hasOption ) { + throw new UnrecognizedOptionException("Unrecognized option: " + arg); + } + else { + opt = (Option) options.getOption( arg ); + } + + // if the option is a required option remove the option from + // the requiredOptions list + if ( opt.isRequired() ) { + requiredOptions.remove( "-" + opt.getOpt() ); + } + + // if the option is in an OptionGroup make that option the selected + // option of the group + if ( options.getOptionGroup( opt ) != null ) { + OptionGroup group = ( OptionGroup ) options.getOptionGroup( opt ); + if( group.isRequired() ) { + requiredOptions.remove( group ); + } + group.setSelected( opt ); + } + + // if the option takes an argument value + if ( opt.hasArg() ) { + processArgs( opt, iter ); + } + + // set the option on the command line + cmd.addOption( opt ); + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java b/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java @@ -0,0 +1,204 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + * Allows Options to be created from a single String. + * + * + * @author Henri Yandell (bayard @ generationjava.com) + * @version $Revision: 2662 $ + */ +public class PatternOptionBuilder { + + /// TODO: These need to break out to OptionType and also to be pluggable. + + /** String class */ + public static final Class STRING_VALUE = java.lang.String.class; + /** Object class */ + public static final Class OBJECT_VALUE = java.lang.Object.class; + /** Number class */ + public static final Class NUMBER_VALUE = java.lang.Number.class; + /** Date class */ + public static final Class DATE_VALUE = java.util.Date.class; + /** Class class */ + public static final Class CLASS_VALUE = java.lang.Class.class; + +/// can we do this one?? +// is meant to check that the file exists, else it errors. +// ie) it's for reading not writing. + /** FileInputStream class */ + public static final Class EXISTING_FILE_VALUE = java.io.FileInputStream.class; + /** File class */ + public static final Class FILE_VALUE = java.io.File.class; + /** File array class */ + public static final Class FILES_VALUE = java.io.File[].class; + /** URL class */ + public static final Class URL_VALUE = java.net.URL.class; + + /** + *

Retrieve the class that ch represents.

+ * + * @param ch the specified character + * @return The class that ch represents + */ + public static Object getValueClass(char ch) { + if (ch == '@') { + return PatternOptionBuilder.OBJECT_VALUE; + } else if (ch == ':') { + return PatternOptionBuilder.STRING_VALUE; + } else if (ch == '%') { + return PatternOptionBuilder.NUMBER_VALUE; + } else if (ch == '+') { + return PatternOptionBuilder.CLASS_VALUE; + } else if (ch == '#') { + return PatternOptionBuilder.DATE_VALUE; + } else if (ch == '<') { + return PatternOptionBuilder.EXISTING_FILE_VALUE; + } else if (ch == '>') { + return PatternOptionBuilder.FILE_VALUE; + } else if (ch == '*') { + return PatternOptionBuilder.FILES_VALUE; + } else if (ch == '/') { + return PatternOptionBuilder.URL_VALUE; + } + return null; + } + + /** + *

Returns whether ch is a value code, i.e. + * whether it represents a class in a pattern.

+ * + * @param ch the specified character + * @return true if ch is a value code, otherwise false. + */ + public static boolean isValueCode(char ch) { + if( (ch != '@') && + (ch != ':') && + (ch != '%') && + (ch != '+') && + (ch != '#') && + (ch != '<') && + (ch != '>') && + (ch != '*') && + (ch != '/') + ) + { + return false; + } + return true; + } + + /** + *

Returns the {@link Options} instance represented by + * pattern.

+ * + * @param pattern the pattern string + * @return The {@link Options} instance + */ + public static Options parsePattern(String pattern) { + int sz = pattern.length(); + + char opt = ' '; + char ch = ' '; + boolean required = false; + Object type = null; + + Options options = new Options(); + + for(int i=0; i. + * + */ +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; + +/** + * The class PosixParser provides an implementation of the + * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2833 $ + */ +public class PosixParser extends Parser { + + /** holder for flattened tokens */ + private ArrayList tokens = new ArrayList(); + /** specifies if bursting should continue */ + private boolean eatTheRest; + /** holder for the current option */ + private Option currentOption; + /** the command line Options */ + private Options options; + + /** + *

Resets the members to their original state i.e. remove + * all of tokens entries, set eatTheRest + * to false and set currentOption to null.

+ */ + private void init() { + eatTheRest = false; + tokens.clear(); + currentOption = null; + } + + /** + *

An implementation of {@link Parser}'s abstract + * {@link Parser#flatten(Options,String[],boolean) flatten} method.

+ * + *

The following are the rules used by this flatten method. + *

    + *
  1. if stopAtNonOption is true then do not + * burst anymore of arguments entries, just add each + * successive entry without further processing. Otherwise, ignore + * stopAtNonOption.
  2. + *
  3. if the current arguments entry is "--" + * just add the entry to the list of processed tokens
  4. + *
  5. if the current arguments entry is "-" + * just add the entry to the list of processed tokens
  6. + *
  7. if the current arguments entry is two characters + * in length and the first character is "-" then check if this + * is a valid {@link Option} id. If it is a valid id, then add the + * entry to the list of processed tokens and set the current {@link Option} + * member. If it is not a valid id and stopAtNonOption + * is true, then the remaining entries are copied to the list of + * processed tokens. Otherwise, the current entry is ignored.
  8. + *
  9. if the current arguments entry is more than two + * characters in length and the first character is "-" then + * we need to burst the entry to determine its constituents. For more + * information on the bursting algorithm see + * {@link PosixParser#burstToken( String, boolean) burstToken}.
  10. + *
  11. if the current arguments entry is not handled + * by any of the previous rules, then the entry is added to the list + * of processed tokens.
  12. + *
+ *

+ * + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed + * @param stopAtNonOption Specifies whether to stop flattening + * when an non option is found. + * @return The flattened arguments String array. + */ + protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) { + init(); + this.options = options; + + // an iterator for the command line tokens + Iterator iter = Arrays.asList(arguments).iterator(); + String token = null; + + // process each command line token + while (iter.hasNext()) { + + // get the next command line token + token = (String) iter.next(); + + // handle SPECIAL TOKEN + if (token.startsWith("--")) { + processLongOptionToken(token, stopAtNonOption); + } else if ("-".equals(token)) { + // single hyphen + processSingleHyphen(token); + } else if (token.startsWith("-")) { + int tokenLength = token.length(); + if (tokenLength == 2) { + processOptionToken(token, stopAtNonOption); + } else { + boolean burst = true; + if (token.length() > 2) { + // check for misspelled long option + String longOptionCandidate = "-" + token; + if (this.options.hasOption(longOptionCandidate)) { + tokens.add(token); + burst = false; + } + } + if (burst) { + burstToken(token, stopAtNonOption); + } + } + } else { + if (stopAtNonOption) { + process(token); + } else { + tokens.add(token); + } + } + + gobble(iter); + } + + return (String[]) tokens.toArray(new String[] {}); + } + + /** + *

Adds the remaining tokens to the processed tokens list.

+ * + * @param iter An iterator over the remaining tokens + */ + private void gobble( Iterator iter ) { + if( eatTheRest ) { + while( iter.hasNext() ) { + tokens.add( iter.next() ); + } + } + } + + /** + *

If there is a current option and it can have an argument + * value then add the token to the processed tokens list and + * set the current option to null.

+ *

If there is a current option and it can have argument + * values then add the token to the processed tokens list.

+ *

If there is not a current option add the special token + * "--" and the current value to the processed + * tokens list. The add all the remaining argument + * values to the processed tokens list.

+ * + * @param value The current token + */ + private void process( String value ) { + if( currentOption != null && currentOption.hasArg() ) { + if( currentOption.hasArg() ) { + tokens.add( value ); + currentOption = null; + } + else if (currentOption.hasArgs() ) { + tokens.add( value ); + } + } + else { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( value ); + } + } + + /** + *

If it is a hyphen then add the hyphen directly to + * the processed tokens list.

+ * + * @param hyphen The hyphen token + */ + private void processSingleHyphen( String hyphen ) { + tokens.add( hyphen ); + } + + /** + *

If an {@link Option} exists for token then + * set the current option and add the token to the processed + * list.

+ *

If an {@link Option} does not exist and stopAtNonOption + * is set then ignore the current token and add the remaining tokens + * to the processed tokens list directly.

+ * + * @param token The current option token + * @param stopAtNonOption Specifies whether flattening should halt + * at the first non option. + */ + private void processOptionToken( String token, boolean stopAtNonOption ) { + if( this.options.hasOption( token ) ) { + currentOption = this.options.getOption( token ); + tokens.add( token ); + } else { + if( stopAtNonOption ) { + eatTheRest = true; + } else { + tokens.add(token); + } + } + } + + /** + * same stop logic as for single hyphen options + * @param longToken + * @param stopAtNonOption + */ + private void processLongOptionToken(String longToken, boolean stopAtNonOption ) { + String token; + String value = null; + if( longToken.indexOf( '=' ) != -1 ) { + token = longToken.substring( 0, longToken.indexOf( '=' )); + value = longToken.substring( longToken.indexOf( '=' ) + 1, longToken.length() ); + } else { + token = longToken; + } + if( this.options.hasOption(token)) { + tokens.add(token); + if( value != null) { + tokens.add(value); + } + } else { + if(stopAtNonOption) { + eatTheRest = true; + } else { + tokens.add(token); + if( value != null) { + tokens.add(value); + } + } + } + } + + + /** + *

Breaks token into its constituent parts + * using the following algorithm. + *

    + *
  • ignore the first character ("-" )
  • + *
  • foreach remaining character check if an {@link Option} + * exists with that id.
  • + *
  • if an {@link Option} does exist then add that character + * prepended with "-" to the list of processed tokens.
  • + *
  • if the {@link Option} can have an argument value and there + * are remaining characters in the token then add the remaining + * characters as a token to the list of processed tokens.
  • + *
  • if an {@link Option} does NOT exist AND + * stopAtNonOption IS set then add the special token + * "--" followed by the remaining characters and also + * the remaining tokens directly to the processed tokens list.
  • + *
  • if an {@link Option} does NOT exist AND + * stopAtNonOption IS NOT set then add that + * character prepended with "-".
  • + *
+ *

+ */ + protected void burstToken( String token, boolean stopAtNonOption ) { + int tokenLength = token.length(); + + for( int i = 1; i < tokenLength; i++) { + String ch = String.valueOf( token.charAt( i ) ); + boolean hasOption = options.hasOption( ch ); + + if( hasOption ) { + tokens.add( "-" + ch ); + currentOption = options.getOption( ch ); + if( currentOption.hasArg() && token.length()!=i+1 ) { + tokens.add( token.substring( i+1 ) ); + break; + } + } + else if( stopAtNonOption ) { + process( token.substring( i ) ); + } + else { + tokens.add( "-" + ch ); + } + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/TypeHandler.java b/installer/src/java/org/apache/commons/cli/TypeHandler.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/TypeHandler.java @@ -0,0 +1,252 @@ +/* + * $Header$ + * $Revision: 2664 $ + * $Date: 2006-02-18 14:20:35 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.io.File; +import java.math.BigDecimal; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Date; + +/** + * This is a temporary implementation. TypeHandler will handle the + * pluggableness of OptionTypes and it will direct all of these types + * of conversion functionalities to ConvertUtils component in Commons + * alreayd. BeanUtils I think. + * + * @author Henri Yandell (bayard @ generationjava.com) + * @version $Revision: 2664 $ + */ +public class TypeHandler { + + /** + *

Returns the Object of type obj + * with the value of str.

+ * + * @param str the command line value + * @param obj the type of argument + * @return The instance of obj initialised with + * the value of str. + */ + public static Object createValue(String str, Object obj) { + return createValue(str, (Class)obj); + } + + /** + *

Returns the Object of type clazz + * with the value of str.

+ * + * @param str the command line value + * @param clazz the type of argument + * @return The instance of clazz initialised with + * the value of str. + */ + public static Object createValue(String str, Class clazz) { + if( PatternOptionBuilder.STRING_VALUE == clazz) { + return str; + } else + if( PatternOptionBuilder.OBJECT_VALUE == clazz) { + return createObject(str); + } else + if( PatternOptionBuilder.NUMBER_VALUE == clazz) { + return createNumber(str); + } else + if( PatternOptionBuilder.DATE_VALUE == clazz) { + return createDate(str); + } else + if( PatternOptionBuilder.CLASS_VALUE == clazz) { + return createClass(str); + } else + if( PatternOptionBuilder.FILE_VALUE == clazz) { + return createFile(str); + } else + if( PatternOptionBuilder.EXISTING_FILE_VALUE == clazz) { + return createFile(str); + } else + if( PatternOptionBuilder.FILES_VALUE == clazz) { + return createFiles(str); + } else + if( PatternOptionBuilder.URL_VALUE == clazz) { + return createURL(str); + } else { + return null; + } + } + + /** + *

Create an Object from the classname and empty constructor.

+ * + * @param str the argument value + * @return the initialised object, or null if it couldn't create the Object. + */ + public static Object createObject(String str) { + Class cl = null; + try { + cl = Class.forName(str); + } catch (ClassNotFoundException cnfe) { + System.err.println("Unable to find: "+str); + return null; + } + + Object instance = null; + + try { + instance = cl.newInstance(); + } catch (InstantiationException cnfe) { + System.err.println("InstantiationException; Unable to create: "+str); + return null; + } + catch (IllegalAccessException cnfe) { + System.err.println("IllegalAccessException; Unable to create: "+str); + return null; + } + + return instance; + } + + /** + *

Create a number from a String.

+ * + * @param str the value + * @return the number represented by str, if str + * is not a number, null is returned. + */ + public static Number createNumber(String str) { + // Needs to be able to create + try { + // do searching for decimal point etc, but atm just make an Integer + return new BigDecimal(str); + } catch (NumberFormatException nfe) { + System.err.println(nfe.getMessage()); + return null; + } + } + + /** + *

Returns the class whose name is str.

+ * + * @param str the class name + * @return The class if it is found, otherwise return null + */ + public static Class createClass(String str) { + try { + return Class.forName(str); + } catch (ClassNotFoundException cnfe) { + System.err.println("Unable to find: "+str); + return null; + } + } + + /** + *

Returns the date represented by str.

+ * + * @param str the date string + * @return The date if str is a valid date string, + * otherwise return null. + */ + public static Date createDate(String str) { + Date date = null; + if(date == null) { + System.err.println("Unable to parse: "+str); + } + return date; + } + + /** + *

Returns the URL represented by str.

+ * + * @param str the URL string + * @return The URL is str is well-formed, otherwise + * return null. + */ + public static URL createURL(String str) { + try { + return new URL(str); + } catch (MalformedURLException mue) { + System.err.println("Unable to parse: "+str); + return null; + } + } + + /** + *

Returns the File represented by str.

+ * + * @param str the File location + * @return The file represented by str. + */ + public static File createFile(String str) { + return new File(str); + } + + /** + *

Returns the File[] represented by str.

+ * + * @param str the paths to the files + * @return The File[] represented by str. + */ + public static File[] createFiles(String str) { +// to implement/port: +// return FileW.findFiles(str); + return null; + } + +} diff --git a/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java b/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Exception thrown during parsing signalling an unrecognized + * option was seen.

+ * + * @author bob mcwhiter (bob @ werken.com) + * @version $Revision: 2662 $ + */ +public class UnrecognizedOptionException extends ParseException { + + /** + *

Construct a new UnrecognizedArgumentException + * with the specified detail message.

+ * + * @param message the detail message + */ + public UnrecognizedOptionException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/python/util/install/AbstractWizard.java b/installer/src/java/org/python/util/install/AbstractWizard.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizard.java @@ -0,0 +1,438 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.HeadlessException; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JSeparator; + +public abstract class AbstractWizard extends JDialog implements ValidationListener { + + private class WizardGlassPane extends JPanel implements MouseListener, KeyListener { + WizardGlassPane() { + super(); + setOpaque(false); + addMouseListener(this); + addKeyListener(this); + } + + public void keyPressed(KeyEvent e) { + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + public void mouseClicked(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseReleased(MouseEvent e) { + } + } + + private AbstractWizardPage _activePage = null; + private JPanel _buttonPanel; + private JSeparator _buttonSeparator; + private Action _cancelAction; + private JButton _cancelButton; + private CardLayout _cards; + private JPanel _content; + private WizardGlassPane _glassPane; + private AbstractWizardHeader _header; + private boolean _headerVisible = false; + private ArrayList _listeners; + private Action _nextAction; + private JButton _nextButton; + private ArrayList _pages; + private Action _previousAction; + private JButton _previousButton; + + public AbstractWizard() { + super(); + initWindow(); + initActions(); + initComponents(); + } + + public AbstractWizard(Dialog parent) throws HeadlessException { + super(parent); + initWindow(); + initActions(); + initComponents(); + } + + public AbstractWizard(Frame parent) throws HeadlessException { + super(parent); + initWindow(); + initActions(); + initComponents(); + } + + public final void addPage(AbstractWizardPage page) { + if (_pages == null) + _pages = new ArrayList(); + if (page == null || _pages.contains(page)) + return; + _pages.add(page); + page.setWizard(this); + int number = _pages.indexOf(page); + _content.add(page, "page" + number); + } + + public final void addWizardListener(WizardListener listener) { + if (listener == null) + return; + if (_listeners == null) + _listeners = new ArrayList(5); + if (_listeners.contains(listener)) + return; + _listeners.add(listener); + } + + private void cancel() { + fireCancelEvent(); + setVisible(false); + } + + /** + * @return whether the wizard finished succesfully + */ + protected abstract boolean finish(); + + private void fireCancelEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardCancelled(event); + } + } + + private void fireFinishedEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardFinished(event); + } + } + + private void fireNextEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardNext(event); + } + } + + private void firePreviousEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardPrevious(event); + } + } + + private void fireStartedEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardStarted(event); + } + } + + /** + * @return the String that will appear on the Cancel button + */ + protected abstract String getCancelString(); + + /** + * @return the String that will appear on the Finish button + */ + protected abstract String getFinishString(); + + /** + * @return the wizard header panel + */ + public AbstractWizardHeader getHeader() { + return _header; + } + + /** + * @return the String that will appear on the Next button + */ + protected abstract String getNextString(); + + /** + * @return the String that will appear on the Previous button + */ + protected abstract String getPreviousString(); + + /** + * usually only called from the WizardValidator, after validation succeeds + * + * validation always occurs when the 'next' or 'finish' button was clicked, so when the validation succeeds, the + * wizard can go to the next page + */ + public final void gotoNextPage() { + next(); + } + + private void initActions() { + _nextAction = new AbstractAction(getNextString()) { + public void actionPerformed(ActionEvent e) { + tryNext(); + } + }; + _previousAction = new AbstractAction(getPreviousString()) { + public void actionPerformed(ActionEvent e) { + previous(); + } + }; + _cancelAction = new AbstractAction(getCancelString()) { + public void actionPerformed(ActionEvent e) { + cancel(); + } + }; + } + + private void initComponents() { + _pages = new ArrayList(); + getContentPane().setLayout(new BorderLayout(0, 0)); + _header = new WizardHeader(); + getContentPane().add(_header, BorderLayout.NORTH); + _content = new JPanel(); + _cards = new CardLayout(); + _content.setLayout(_cards); + + getContentPane().add(_content, BorderLayout.CENTER); // was: WEST + + _buttonPanel = new JPanel(); + _buttonSeparator = new JSeparator(); + _cancelButton = new JButton(); + _previousButton = new JButton(); + _nextButton = new JButton(); + _cancelButton.setAction(_cancelAction); + _previousButton.setAction(_previousAction); + _nextButton.setAction(_nextAction); + GridBagConstraints gridBagConstraints; + _buttonPanel.setLayout(new GridBagLayout()); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new Insets(1, 1, 1, 1); + gridBagConstraints.weightx = 1.0; + _buttonPanel.add(_buttonSeparator, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + _buttonPanel.add(_cancelButton, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + _buttonPanel.add(_previousButton, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + _buttonPanel.add(_nextButton, gridBagConstraints); + getContentPane().add(_buttonPanel, BorderLayout.SOUTH); + } + + private void initWindow() { + _glassPane = new WizardGlassPane(); + setGlassPane(_glassPane); + } + + /** + * @return whether the wizard header is visible + */ + public final boolean isHeaderVisible() { + return _headerVisible; + } + + /** + * lock the wizard dialog, preventing any user input + */ + public final void lock() { + _glassPane.setVisible(true); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + + private void next() { + if (_activePage == null) + return; + _activePage.passivate(); + int activeIndex = _pages.indexOf(_activePage); + int nextIndex = activeIndex + 1; + if (nextIndex >= _pages.size()) { + tryFinish(); + return; + } else { + _activePage = (AbstractWizardPage) _pages.get(nextIndex); + showActivePage(); + } + fireNextEvent(); + } + + private void previous() { + if (_activePage == null) + return; + _activePage.passivate(); + int activeIndex = _pages.indexOf(_activePage); + int previousIndex = activeIndex - 1; + if (previousIndex < 0) + return; + else { + _activePage = (AbstractWizardPage) _pages.get(previousIndex); + showActivePage(); + } + firePreviousEvent(); + } + + public final void removeWizardListener(WizardListener listener) { + if (listener == null || _listeners == null || !_listeners.contains(listener)) + return; + _listeners.remove(listener); + } + + /** + * @param header the wizard header panel + */ + public void setHeader(AbstractWizardHeader header) { + if (this._header != null) { + getContentPane().remove(header); + } + this._header = header; + if (this._header != null) { + getContentPane().add(header, BorderLayout.NORTH); + } + } + + /** + * @param visible show the header in this wizard? + */ + public final void setHeaderVisible(boolean visible) { + _headerVisible = visible; + if (_header != null) + _header.setVisible(visible); + } + + @Override + public void setVisible(boolean visible) { + if (visible) { + fireStartedEvent(); + if (_pages.size() > 0) { + _activePage = (AbstractWizardPage) _pages.get(0); + showActivePage(); + } + } + super.setVisible(visible); + } + + private void showActivePage() { + if (_activePage == null) + return; + int number = _pages.indexOf(_activePage); + // show the active page + _cards.show(_content, "page" + number); + // update the wizard header + if (_header != null) { + _header.setTitle(_activePage.getTitle()); + _header.setDescription(_activePage.getDescription()); + _header.setIcon(_activePage.getIcon()); + } + // set visibility and localized text of buttons + if (number == 0) { + _previousButton.setVisible(false); + } else { + _previousButton.setVisible(_activePage.isPreviousVisible()); + } + _previousAction.putValue(Action.NAME, getPreviousString()); + _cancelButton.setVisible(_activePage.isCancelVisible()); + _cancelAction.putValue(Action.NAME, getCancelString()); + _nextButton.setVisible(_activePage.isNextVisible()); + _nextAction.putValue(Action.NAME, getNextString()); + if (number + 1 == _pages.size()) { + _nextAction.putValue(Action.NAME, getFinishString()); + } else { + _nextAction.putValue(Action.NAME, getNextString()); + } + if (_nextButton.isVisible()) { + getRootPane().setDefaultButton(_nextButton); + // workaround wrong default button (e.g. on OverviewPage) + if (_activePage.getFocusField() == null) { + _nextButton.grabFocus(); + } + } + _activePage.doActivate(); + } + + private void tryFinish() { + if (finish()) { + this.setVisible(false); + fireFinishedEvent(); + } + } + + private void tryNext() { + if (_activePage == null) + return; + _activePage.validateInput(); + } + + /** + * unlock the wizard dialog, allowing user input + */ + public final void unlock() { + _glassPane.setVisible(false); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardHeader.java b/installer/src/java/org/python/util/install/AbstractWizardHeader.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardHeader.java @@ -0,0 +1,12 @@ +package org.python.util.install; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; + +abstract class AbstractWizardHeader extends JPanel { + protected abstract void setTitle(String title); + + protected abstract void setDescription(String description); + + protected abstract void setIcon(ImageIcon icon); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardPage.java b/installer/src/java/org/python/util/install/AbstractWizardPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardPage.java @@ -0,0 +1,153 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.net.URL; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JPanel; + +public abstract class AbstractWizardPage extends JPanel implements TextKeys { + private static final long serialVersionUID = -5233805023557214279L; + + private static final String _ICON_FILE_NAME = "jython_small_c.png"; + + private static ImageIcon _imageIcon = null; + private AbstractWizardValidator _validator = null; + private AbstractWizard _wizard; + + public AbstractWizardPage() { + super(); + setValidator(null); + } + + /** + * This method is called when the wizard page is activated, after Wizard.next(); + */ + protected abstract void activate(); + + /** + * This method is called right before validation of the page + */ + protected abstract void beforeValidate(); + + /** + * called from Wizard, right after this page is set visible + */ + final void doActivate() { + if (getFocusField() != null) + this.getFocusField().grabFocus(); + activate(); + } + + /** + * @return the description of this page, which will be displayed in the wizard header + */ + protected abstract String getDescription(); + + /** + * @return the input field on the page that should grab the focus when the page is activated + */ + protected abstract JComponent getFocusField(); + + /** + * @return the icon that should be displayed in the header in all steps + */ + protected ImageIcon getIcon() { + if (_imageIcon == null) { + URL iconURL = FileHelper.getRelativeURL(getClass(), _ICON_FILE_NAME); + if (iconURL != null) { + _imageIcon = new ImageIcon(iconURL); + } + } + return _imageIcon; + } + + /** + * @return the title of this page, which will be displayed in the wizard header + */ + protected abstract String getTitle(); + + /** + * @return the wizard this page belongs to + */ + public final AbstractWizard getWizard() { + return _wizard; + } + + /** + * @return whether the cancel button is visible + */ + protected abstract boolean isCancelVisible(); + + /** + * @return whether the next button is visible + */ + protected abstract boolean isNextVisible(); + + /** + * @return whether the previous button is visible + */ + protected abstract boolean isPreviousVisible(); + + /** + * this method is called right before the page is hidden, but after the validation + */ + protected abstract void passivate(); + + /** + * Set the validator for this page. The validator is called when the next button is clicked. + * + * @param validator the validator for this page. If this is null, a EmptyValidator is assigned + */ + public final void setValidator(AbstractWizardValidator validator) { + if (validator == null) + this._validator = new EmptyValidator(); + else + this._validator = validator; + this._validator.setWizardPage(this); + this._validator.addValidationListener(_wizard); + } + + /** + * @param wizard the wizard this page belongs to + */ + final void setWizard(AbstractWizard wizard) { + this._wizard = wizard; + _validator.addValidationListener(wizard); + } + + /** + * perform the validation of this page, using the assigned WizardValidator + */ + final void validateInput() { + beforeValidate(); + if (_validator == null) + return; + _validator.start(); + } + + /** + * @return default grid bag constraints + */ + protected GridBagConstraints newGridBagConstraints() { + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + return gridBagConstraints; + } + + final String getText(String textKey) { + return Installation.getText(textKey); + } + + final String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + + final String getText(String textKey, String parameter0, String parameter1) { + return Installation.getText(textKey, parameter0, parameter1); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardValidator.java b/installer/src/java/org/python/util/install/AbstractWizardValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardValidator.java @@ -0,0 +1,132 @@ +package org.python.util.install; + +import java.util.ArrayList; +import java.util.Iterator; + +public abstract class AbstractWizardValidator implements TextKeys { + + /** + * The thread that performs the validation + */ + private class ValidatorThread extends Thread { + public final void run() { + try { + fireValidationStarted(); + validate(); + fireValidationSucceeded(); + } catch (ValidationException e) { + fireValidationFailed(e); + } catch (ValidationInformationException vie) { + fireValidationInformationRequired(vie); + } + } + } + + private ArrayList _listeners; + private AbstractWizardPage _page; + private Thread _validatorThread; + + public final void addValidationListener(ValidationListener listener) { + if (listener == null) + return; + if (_listeners == null) + _listeners = new ArrayList(5); + if (_listeners.contains(listener)) + return; + _listeners.add(listener); + } + + private void fireValidationFailed(ValidationException exception) { + if (getWizard() != null) + getWizard().unlock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationFailed(event, exception); + } + } + + private void fireValidationInformationRequired(ValidationInformationException exception) { + if (getWizard() != null) + getWizard().unlock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationInformationRequired(event, exception); + } + } + + private void fireValidationStarted() { + if (getWizard() != null) + getWizard().lock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationStarted(event); + } + } + + private void fireValidationSucceeded() { + if (getWizard() != null) { + getWizard().unlock(); + getWizard().gotoNextPage(); + } + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationSucceeded(event); + } + } + + protected final AbstractWizard getWizard() { + if (_page != null) { + return _page.getWizard(); + } else { + return null; + } + } + + protected final AbstractWizardPage getWizardPage() { + return _page; + } + + public final void removeValidationListener(ValidationListener listener) { + if (listener == null || _listeners == null || !_listeners.contains(listener)) + return; + _listeners.remove(listener); + } + + public final void setWizardPage(AbstractWizardPage page) { + this._page = page; + } + + public final void start() { + _validatorThread = new ValidatorThread(); + _validatorThread.start(); + } + + /** + * perform the actual validation + * + * @throws ValidationException when the validation failed + * @throws ValidationInformationException when an information should be displayed + */ + protected abstract void validate() throws ValidationException, ValidationInformationException; + + final String getText(String textKey) { + return Installation.getText(textKey); + } + + final String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ChildProcess.java b/installer/src/java/org/python/util/install/ChildProcess.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ChildProcess.java @@ -0,0 +1,357 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Easy start of a child process. + *

+ * Features are: + *

    + *
  • wait for the child process to finish. + *
  • kill the child process after a specified timeout. + *
  • get the output of the child process (System.out and System.err) redirected to the calling + * process, unless in silent mode. + *
+ */ +public class ChildProcess { + + /** + * Inner class for reading stdout of the child process and printing it onto the caller's stdout. + */ + private class StdoutMonitor extends Thread { + + private StdoutMonitor() {} + + public void run() { + String line = null; + BufferedReader stdout = new BufferedReader(new InputStreamReader(_process.getInputStream())); + try { + // blocks until input found or process dead + while ((line = stdout.readLine()) != null) { + if (!isSilent()) { + System.out.println(line); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } finally { + if (stdout != null) + try { + stdout.close(); + } catch (IOException e) {} + } + } + } + + /** + * Inner class for reading stderr of the child process and printing it onto the caller's stderr. + */ + private class StderrMonitor extends Thread { + + private StderrMonitor() {} + + public void run() { + String line = null; + BufferedReader stderr = new BufferedReader(new InputStreamReader(_process.getErrorStream())); + try { + // blocks until input found or process dead + while ((line = stderr.readLine()) != null) { + if (!isSilent()) { + System.err.println(line); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } finally { + if (stderr != null) + try { + stderr.close(); + } catch (IOException e) {} + } + } + } + + /** + * Constant indicating no timeout at all. + */ + public static final long INFINITE_TIMEOUT = -1; + + /** + * Constant indicating the exit value if the child process was destroyed due to a timeout. + */ + public static final int DESTROYED_AFTER_TIMEOUT = -9898; + + /** + * Constant indicating that the exit value was not yet set + */ + private static final int NOT_SET_EXITVALUE = -9; + + /** + * The command as an array of strings + */ + private String _command[] = null; + + /** + * The timeout (in milliseconds) + */ + private long _timeout = INFINITE_TIMEOUT; + + /** + * The interval for checking if the child process is still alive + */ + private long _pollAliveInterval = 1000; + + /** + * the effective child process + */ + Process _process; + + /** + * the exit value + */ + private int _exitValue = NOT_SET_EXITVALUE; + + /** + * The start time of the child process + */ + private long _startTime; + + /** + * debug option (default is false) + */ + private boolean _debug = false; + + /** + * silent flag + */ + private boolean _silent = false; + + /** + * Default constructor + */ + public ChildProcess() {} + + /** + * Constructor taking a command array as an argument + * + * @param command + * The command to be executed, every token as array element. + */ + public ChildProcess(String command[]) { + setCommand(command); + } + + /** + * Constructor taking a command array and the timeout as an argument + * + * @param command + * The command to be executed, every token as array element. + * @param timeout + * in milliseconds. Special value: INFINITE_TIMEOUT indicates no timeout + * at all. + */ + public ChildProcess(String command[], long timeout) { + setCommand(command); + setTimeout(timeout); + } + + /** + * Set the command array. This will override (but not overwrite) a previously set command + */ + public void setCommand(String command[]) { + _command = command; + } + + /** + * Returns the command array + */ + public String[] getCommand() { + return _command; + } + + /** + * Set the timeout (how long should the calling process wait for the child). + * + * @param timeout + * in milliseconds. Special value: INFINITE_TIMEOUT indicates no timeout + * at all. This is the default. + */ + public void setTimeout(long timeout) { + _timeout = timeout; + } + + /** + * Returns the timeout in milliseconds. + */ + public long getTimeout() { + return _timeout; + } + + /** + * Set the debug flag. + *

+ * Setting this to true will print the submitted command and an information if the child process + * is destroyed after the timeout. + */ + public void setDebug(boolean debug) { + _debug = debug; + } + + /** + * Returns the debug flag + */ + public boolean isDebug() { + return _debug; + } + + /** + * Set the silent flag. + *

+ * Setting this to true will suppress output of the called command. + */ + public void setSilent(boolean silent) { + _silent = silent; + } + + /** + * Returns the silent flag. + */ + public boolean isSilent() { + return _silent; + } + + /** + * Set the interval (in milliseconds) after which the subprocess is checked if it is still + * alive. Defaults to 1000 ms. + */ + public void setPollAliveInterval(long pollAliveInterval) { + _pollAliveInterval = pollAliveInterval; + } + + /** + * Returns the interval (in milliseconds) after which the subprocess is checked if it is still + * alive. + */ + public long getPollAliveInterval() { + return _pollAliveInterval; + } + + /** + * returns true if the timeout has expired + */ + private boolean isTimeout() { + boolean isTimeout = false; + long currentTime = System.currentTimeMillis(); + long diff = 0; + long timeout = getTimeout(); + if (timeout != INFINITE_TIMEOUT) { + diff = currentTime - _startTime; + if (diff > timeout) { + isTimeout = true; + } + } + return isTimeout; + } + + /** + * Start the child process + */ + public int run() { + try { + // determine start time + _startTime = System.currentTimeMillis(); + // start the process + _process = Runtime.getRuntime().exec(getCommand()); + debugCommand(); + // handle stdout and stderr + StdoutMonitor stdoutMonitor = new StdoutMonitor(); + stdoutMonitor.start(); + StderrMonitor stderrMonitor = new StderrMonitor(); + stderrMonitor.start(); + // run the subprocess as long as wanted + while (!isTimeout() && isAlive()) { + try { + Thread.sleep(getPollAliveInterval()); + } catch (InterruptedException ie) { + if (!isSilent()) { + ie.printStackTrace(); + } + } + } + // end properly + if (isAlive()) { // sets the exit value in case process is dead + destroy(); + } else { + if (isDebug()) { + System.out.println("[ChildProcess] ended itself"); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } + return getExitValue(); + } + + /** + * The exit value + */ + public int getExitValue() { + return _exitValue; + } + + private void setExitValue(int exitValue) { + _exitValue = exitValue; + } + + /** + * Tests if the process is still alive + */ + private boolean isAlive() { + try { + setExitValue(_process.exitValue()); + return false; + } catch (IllegalThreadStateException itse) { + return true; + } + } + + /** + * Destroy the child process + */ + private void destroy() { + _process.destroy(); + setExitValue(DESTROYED_AFTER_TIMEOUT); + if (isDebug()) { + System.out.println("[ChildProcess] destroying because of timeout !"); + } + } + + /** + * Lists the submitted command (if so indicated) + */ + private void debugCommand() { + if (isDebug()) { + String[] command = getCommand(); + if (command != null) { + System.out.print("[ChildProcess] command '"); + for (int i = 0; i < command.length; i++) { + String commandPart = command[i]; + if (i == 0) { + System.out.print(commandPart); + } else { + System.out.print(" " + commandPart); + } + } + System.out.println("' is now running..."); + } + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ConsoleInstaller.java b/installer/src/java/org/python/util/install/ConsoleInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ConsoleInstaller.java @@ -0,0 +1,609 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.python.util.install.Installation.JavaVersionInfo; +import org.python.util.install.driver.Tunnel; + +public class ConsoleInstaller implements ProgressListener, TextKeys { + + public static final String CURRENT_JRE = "="; + + private static final String _CANCEL = "c"; + + private static final String _PROMPT = ">>>"; + + private static final String _BEGIN_ANSWERS = "["; + + private static final String _END_ANSWERS = "]"; + + + private InstallerCommandLine _commandLine; + + private JarInstaller _jarInstaller; + + private JarInfo _jarInfo; + + private Tunnel _tunnel; + + public ConsoleInstaller(InstallerCommandLine commandLine, JarInfo jarInfo) { + _commandLine = commandLine; + _jarInfo = jarInfo; + _jarInstaller = new JarInstaller(this, jarInfo); + } + + public void setTunnel(Tunnel tunnel) { + _tunnel = tunnel; + } + + public void install() { + File targetDirectory = null; + JavaHomeHandler javaHomeHandler = null; + if (_commandLine.hasConsoleOption()) { + welcome(); + selectLanguage(); + acceptLicense(); + InstallationType installationType = selectInstallationType(); + targetDirectory = determineTargetDirectory(); + javaHomeHandler = checkVersion(determineJavaHome()); + promptForCopying(targetDirectory, installationType, javaHomeHandler); + _jarInstaller.inflate(targetDirectory, installationType, javaHomeHandler); + showReadme(targetDirectory); + success(targetDirectory); + } else if (_commandLine.hasSilentOption()) { + message(getText(C_SILENT_INSTALLATION)); + targetDirectory = _commandLine.getTargetDirectory(); + checkTargetDirectorySilent(targetDirectory); + javaHomeHandler = checkVersionSilent(_commandLine.getJavaHomeHandler()); + _jarInstaller.inflate(targetDirectory, + _commandLine.getInstallationType(), + javaHomeHandler); + success(targetDirectory); + } + } + + protected final static void message(String message) { + System.out.println(message); // this System.out.println is intended + } + + private void welcome() { + message(getText(C_WELCOME_TO_JYTHON)); + message(getText(C_VERSION_INFO, _jarInfo.getVersion())); + message(getText(C_AT_ANY_TIME_CANCEL, _CANCEL)); + } + + private String question(String question) { + return question(question, null, false, null); + } + + private String question(String question, boolean answerRequired) { + return question(question, null, answerRequired, null); + } + + private String question(String question, List answers, String defaultAnswer) { + return question(question, answers, true, defaultAnswer); + } + + /** + * question and answer + * + * @param question + * @param answers + * Possible answers (may be null) + * @param answerRequired + * @param defaultAnswer + * (may be null) + * + * @return (chosen) answer + */ + private String question(String question, + List answers, + boolean answerRequired, + String defaultAnswer) { + try { + if (answers != null && answers.size() > 0) { + question = question + " " + _BEGIN_ANSWERS; + Iterator answersAsIterator = answers.iterator(); + while (answersAsIterator.hasNext()) { + if (!question.endsWith(_BEGIN_ANSWERS)) + question = question + "/"; + String possibleAnswer = answersAsIterator.next(); + if (possibleAnswer.equalsIgnoreCase(defaultAnswer)) { + if (Character.isDigit(possibleAnswer.charAt(0))) { + question = question.concat(" ").concat(possibleAnswer).concat(" "); + } else { + question = question + possibleAnswer.toUpperCase(); + } + } else { + question = question + possibleAnswer; + } + } + question = question + _END_ANSWERS; + } + question = question + " " + _PROMPT + " "; + boolean match = false; + String answer = ""; + while (!match && !_CANCEL.equalsIgnoreCase(answer)) { + // output to normal System.out + System.out.print(question); // intended print, not println (!) + answer = readLine(); + if ("".equals(answer) && answerRequired) { + // check default answer + if (defaultAnswer != null) { + match = true; + answer = defaultAnswer; + } + } else { + if (answers != null && answers.size() > 0) { + Iterator answersAsIterator = answers.iterator(); + while (answersAsIterator.hasNext()) { + if (answer.equalsIgnoreCase(answersAsIterator.next())) { + match = true; + } + } + } else { + match = true; + } + if (!match && !_CANCEL.equalsIgnoreCase(answer)) { + message(getText(C_INVALID_ANSWER, answer)); + } + } + } + if (_CANCEL.equalsIgnoreCase(answer)) { + throw new InstallationCancelledException(); + } + return answer; + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + /** + * Send a signal through the tunnel, and then wait for the answer from the other side. + * + *

+     *            (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *            (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * 
+ */ + private String readLine() throws IOException { + InputStream inputStream; + String line = ""; + if (_tunnel == null) { + inputStream = System.in; + } else { + inputStream = _tunnel.getAnswerReceiverStream(); + _tunnel.getQuestionSenderStream().write(Tunnel.NEW_LINE.getBytes()); + _tunnel.getQuestionSenderStream().flush(); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + line = reader.readLine(); + return line; + } + + private void selectLanguage() { + List availableLanguages = new ArrayList(2); + availableLanguages.add(getText(C_ENGLISH)); + availableLanguages.add(getText(C_GERMAN)); // 1 == German + List answers = new ArrayList(availableLanguages.size()); + String languages = ""; + String defaultAnswer = null; + for (Iterator iterator = availableLanguages.iterator(); iterator.hasNext();) { + String language = iterator.next(); + String possibleAnswer = language.substring(0, 1); + if (defaultAnswer == null) { + defaultAnswer = possibleAnswer; + } + languages = languages + language + ", "; + answers.add(possibleAnswer.toLowerCase()); + } + languages = languages.substring(0, languages.length() - 2); + message(getText(C_AVAILABLE_LANGUAGES, languages)); + String answer = question(getText(C_SELECT_LANGUAGE), answers, defaultAnswer); + if (answer.equalsIgnoreCase(answers.get(1))) { + Installation.setLanguage(Locale.GERMAN); + } else { + Installation.setLanguage(Locale.ENGLISH); + } + } + + private InstallationType selectInstallationType() { + InstallationType installationType = new InstallationType(); + String no = getText(C_NO); + String yes = getText(C_YES); + message(getText(C_INSTALL_TYPES)); + message(" " + Installation.ALL + ". " + getText(C_ALL)); + message(" " + Installation.STANDARD + ". " + getText(C_STANDARD)); + message(" " + Installation.MINIMUM + ". " + getText(C_MINIMUM)); + message(" " + Installation.STANDALONE + ". " + getText(C_STANDALONE)); + String answer = question(getText(C_SELECT_INSTALL_TYPE), getTypeAnswers(), Installation.ALL); + if (Installation.ALL.equals(answer)) { + installationType.setAll(); + } else if (Installation.STANDARD.equals(answer)) { + installationType.setStandard(); + } else if (Installation.MINIMUM.equals(answer)) { + installationType.setMinimum(); + } else if (Installation.STANDALONE.equals(answer)) { + installationType.setStandalone(); + } + if (!installationType.isStandalone()) { + // include parts ? + if (!installationType.isAll()) { + answer = question(getText(C_INCLUDE), getYNAnswers(), no); + if (yes.equals(answer)) { + do { + answer = question(getText(C_INEXCLUDE_PARTS, no), getInExcludeAnswers(), no); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(answer)) { + installationType.addLibraryModules(); + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(answer)) { + installationType.addDemosAndExamples(); + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(answer)) { + installationType.addDocumentation(); + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(answer)) { + installationType.addSources(); + } + if (!no.equals(answer)) { + message(getText(C_SCHEDULED, answer)); + } + } while (!no.equals(answer)); + } + } + // exclude parts ? + if (!installationType.isMinimum()) { + answer = question(getText(C_EXCLUDE), getYNAnswers(), no); + if (yes.equals(answer)) { + do { + answer = question(getText(C_INEXCLUDE_PARTS, no), getInExcludeAnswers(), no); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(answer)) { + installationType.removeLibraryModules(); + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(answer)) { + installationType.removeDemosAndExamples(); + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(answer)) { + installationType.removeDocumentation(); + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(answer)) { + installationType.removeSources(); + } + if (!no.equals(answer)) { + message(getText(C_UNSCHEDULED, answer)); + } + } while (!no.equals(answer)); + } + } + } + return installationType; + } + + private JavaHomeHandler checkVersion(JavaHomeHandler javaHomeHandler) { + // handle target java version + JavaInfo javaInfo = verifyTargetJava(javaHomeHandler); + message(getText(C_JAVA_VERSION, + javaInfo.getJavaVersionInfo().getVendor(), + javaInfo.getJavaVersionInfo().getVersion())); + if (!Installation.isValidJava(javaInfo.getJavaVersionInfo())) { + message(getText(C_UNSUPPORTED_JAVA)); + question(getText(C_PROCEED_ANYWAY)); + } + // handle OS + String osName = System.getProperty(Installation.OS_NAME); + String osVersion = System.getProperty(Installation.OS_VERSION); + message(getText(C_OS_VERSION, osName, osVersion)); + if (!Installation.isValidOs()) { + message(getText(C_UNSUPPORTED_OS)); + question(getText(C_PROCEED_ANYWAY)); + } + return javaInfo.getJavaHomeHandler(); + } + + private JavaHomeHandler checkVersionSilent(JavaHomeHandler javaHomeHandler) { + // check target java version + JavaInfo javaInfo = verifyTargetJava(javaHomeHandler); + if (!Installation.isValidJava(javaInfo.getJavaVersionInfo())) { + message(getText(C_UNSUPPORTED_JAVA)); + } + // check OS + if (!Installation.isValidOs()) { + message(getText(C_UNSUPPORTED_OS)); + } + return javaInfo.getJavaHomeHandler(); + } + + private JavaInfo verifyTargetJava(JavaHomeHandler javaHomeHandler) { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); // a priori + if (javaHomeHandler.isDeviation()) { + javaVersionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (javaVersionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + // switch back to current if an error occurred + message(getText(C_TO_CURRENT_JAVA, javaVersionInfo.getReason())); + javaHomeHandler = new JavaHomeHandler(); + } + } + JavaInfo javaInfo = new JavaInfo(); + javaInfo.setJavaHomeHandler(javaHomeHandler); + javaInfo.setJavaVersionInfo(javaVersionInfo); + return javaInfo; + } + + private void acceptLicense() { + String no = getText(C_NO); + String yes = getText(C_YES); + String read = question(getText(C_READ_LICENSE), getYNAnswers(), no); + if (read.equalsIgnoreCase(getText(C_YES))) { + String licenseText = "n/a"; + try { + licenseText = _jarInfo.getLicenseText(); + message(licenseText); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + String accept = question(getText(C_ACCEPT), getYNAnswers(), yes); + if (!accept.equalsIgnoreCase(getText(C_YES))) { + throw new InstallationCancelledException(); + } + } + + private File determineTargetDirectory() { + String no = getText(C_NO); + String yes = getText(C_YES); + File targetDirectory = null; + try { + do { + targetDirectory = new File(question(getText(C_ENTER_TARGET_DIRECTORY), true)); + if (targetDirectory.exists()) { + if (!targetDirectory.isDirectory()) { + message(getText(C_NOT_A_DIRECTORY, targetDirectory.getCanonicalPath())); + } else { + if (targetDirectory.list().length > 0) { + String overwrite = question(getText(C_OVERWRITE_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + no); + if (overwrite.equalsIgnoreCase(getText(C_YES))) { + String clear = question(getText(C_CLEAR_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + yes); + if (clear.equalsIgnoreCase(getText(C_YES))) { + clearDirectory(targetDirectory); + } + } + } + } + } else { + String create = question(getText(C_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + yes); + if (create.equalsIgnoreCase(getText(C_YES))) { + if (!targetDirectory.mkdirs()) { + throw new InstallerException(getText(C_UNABLE_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } + } + } while (!targetDirectory.exists() || !targetDirectory.isDirectory() + || targetDirectory.list().length > 0); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + return targetDirectory; + } + + private JavaHomeHandler determineJavaHome() { + JavaHomeHandler javaHomeHandler = null; + boolean javaFound = false; + while (!javaFound) { + String javaHomeName = question(getText(C_ENTER_JAVA_HOME), null, true, CURRENT_JRE); + // only validate deviations + if (CURRENT_JRE.equals(javaHomeName)) { + javaHomeHandler = new JavaHomeHandler(); + javaFound = true; + } else { + javaHomeHandler = new JavaHomeHandler(javaHomeName); + if (javaHomeHandler.isDeviation()) { + if (!javaHomeHandler.isValidHome()) { + String binDirName = javaHomeName.concat("/bin"); + message(getText(C_NO_JAVA_EXECUTABLE, binDirName)); + } else { + javaFound = true; + } + } else { + javaFound = true; + } + } + } + return javaHomeHandler; + } + + private void checkTargetDirectorySilent(File targetDirectory) { + try { + if (!targetDirectory.exists()) { + // create directory + if (!targetDirectory.mkdirs()) { + throw new InstallerException(getText(C_UNABLE_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } else { + // assert it is an empty directory + if (!targetDirectory.isDirectory()) { + throw new InstallerException(getText(C_NOT_A_DIRECTORY, + targetDirectory.getCanonicalPath())); + } else { + if (targetDirectory.list().length > 0) { + throw new InstallerException(getText(C_NON_EMPTY_TARGET_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } + } + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + private void showReadme(final File targetDirectory) { + String no = getText(C_NO); + String read = question(getText(C_READ_README), getYNAnswers(), no); + if (read.equalsIgnoreCase(getText(C_YES))) { + try { + message(_jarInfo.getReadmeText()); + question(getText(C_PROCEED)); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + } + + private void clearDirectory(File targetDirectory) { + File files[] = targetDirectory.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + clearDirectory(files[i]); + } + if (!files[i].delete()) { + throw new InstallerException(getText(C_UNABLE_TO_DELETE, files[i].getAbsolutePath())); + } + } + } + + private void promptForCopying(final File targetDirectory, + final InstallationType installationType, + final JavaHomeHandler javaHomeHandler) { + try { + message(getText(C_SUMMARY)); + if (installationType.isStandalone()) { + message(" - " + InstallerCommandLine.TYPE_STANDALONE); + } else { + message(" - " + InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES + ": " + + installationType.installLibraryModules()); + message(" - " + InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES + ": " + + installationType.installDemosAndExamples()); + message(" - " + InstallerCommandLine.INEXCLUDE_DOCUMENTATION + ": " + + installationType.installDocumentation()); + message(" - " + InstallerCommandLine.INEXCLUDE_SOURCES + ": " + + installationType.installSources()); + if (javaHomeHandler.isValidHome()) { + message(" - JRE: " + javaHomeHandler.getHome().getAbsolutePath()); + } else { + message(" - java"); + } + } + String proceed = question(getText(C_CONFIRM_TARGET, targetDirectory.getCanonicalPath()), + getYNAnswers(), getText(C_YES)); + if (!proceed.equalsIgnoreCase(getText(C_YES))) { + throw new InstallationCancelledException(); + } + } catch (IOException ioe) { + throw new InstallerException(ioe); // catch for the compiler + } + } + + private void success(final File targetDirectory) { + try { + message(getText(C_CONGRATULATIONS) + " " + + getText(C_SUCCESS, _jarInfo.getVersion(), targetDirectory.getCanonicalPath())); + } catch (IOException ioe) { + throw new InstallerException(ioe); // catch for the compiler + } + } + + private List getTypeAnswers() { + List answers = new ArrayList(4); + answers.add(Installation.ALL); + answers.add(Installation.STANDARD); + answers.add(Installation.MINIMUM); + answers.add(Installation.STANDALONE); + return answers; + } + + private List getYNAnswers() { + List answers = new ArrayList(2); + answers.add(getText(C_YES)); + answers.add(getText(C_NO)); + return answers; + } + + private List getInExcludeAnswers() { + List answers = new ArrayList(5); + answers.add(InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES); + answers.add(InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES); + answers.add(InstallerCommandLine.INEXCLUDE_DOCUMENTATION); + answers.add(InstallerCommandLine.INEXCLUDE_SOURCES); + answers.add(getText(C_NO)); + return answers; + } + + private void progressMessage(int percentage) { + message(" " + percentage + " %"); + } + + private String getText(String textKey) { + return Installation.getText(textKey); + } + + private String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + + private String getText(String textKey, String parameter0, String parameter1) { + return Installation.getText(textKey, parameter0, parameter1); + } + + private static class JavaInfo { + + private JavaVersionInfo _javaVersionInfo; + + private JavaHomeHandler _javaHomeHandler; + + void setJavaHomeHandler(JavaHomeHandler javaHomeHandler) { + _javaHomeHandler = javaHomeHandler; + } + + JavaHomeHandler getJavaHomeHandler() { + return _javaHomeHandler; + } + + void setJavaVersionInfo(JavaVersionInfo javaVersionInfo) { + _javaVersionInfo = javaVersionInfo; + } + + JavaVersionInfo getJavaVersionInfo() { + return _javaVersionInfo; + } + } + + // + // interface ProgressListener + // + public void progressChanged(int newPercentage) { + progressMessage(newPercentage); + } + + public int getInterval() { + return 10; // fixed interval for console installer + } + + public void progressFinished() { + progressMessage(100); + } + + public void progressEntry(String entry) { + // ignore the single entries - only used in gui mode + } + + public void progressStartScripts() { + message(getText(C_GENERATING_START_SCRIPTS)); + } + + public void progressStandalone() { + message(getText(C_PACKING_STANDALONE_JAR)); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/DirectoryFilter.java b/installer/src/java/org/python/util/install/DirectoryFilter.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectoryFilter.java @@ -0,0 +1,23 @@ +package org.python.util.install; + +import java.io.File; + +import javax.swing.filechooser.FileFilter; + +/** + * Filters all directories, and sets the description in the file chooser + */ +public class DirectoryFilter extends FileFilter { + + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } else { + return false; + } + } + + public String getDescription() { + return Installation.getText(TextKeys.DIRECTORIES_ONLY); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/DirectorySelectionPage.java b/installer/src/java/org/python/util/install/DirectorySelectionPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectorySelectionPage.java @@ -0,0 +1,200 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +public class DirectorySelectionPage extends AbstractWizardPage { + + private static final long serialVersionUID = -3672273150338356549L; + + private JLabel _label; + private JTextField _directory; + private JButton _browse; + private JarInfo _jarInfo; + + public DirectorySelectionPage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + // label + _label = new JLabel(); + // directory + _directory = new JTextField(40); + _directory.addFocusListener(new DirectoryFocusListener()); + // browse button + _browse = new JButton(); + _browse.addActionListener(new BrowseButtonListener()); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(_directory, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(_browse, gridBagConstraints); + + add(panel); + } + + JTextField getDirectory() { + return _directory; + } + + protected String getTitle() { + return getText(TARGET_DIRECTORY_PROPERTY); + } + + protected String getDescription() { + return getText(CHOOSE_LOCATION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _directory; + } + + protected void activate() { + _label.setText(getText(SELECT_TARGET_DIRECTORY) + ": "); + _browse.setText(getText(BROWSE)); + String directory = FrameInstaller.getTargetDirectory(); + if (directory == null || directory.length() <= 0) { + File defaultDirectory = getDefaultDirectory(); + try { + directory = defaultDirectory.getCanonicalPath(); + } catch (IOException e) { + directory = "?"; + } + FrameInstaller.setTargetDirectory(directory); + } + _directory.setText(FrameInstaller.getTargetDirectory()); + _directory.setToolTipText(_directory.getText()); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private File getDefaultDirectory() { + String directory = ""; + File defaultDirectory = null; + // 1st try (on windows): root + if (Installation.isWindows()) { + JavaHomeHandler handler = new JavaHomeHandler(); + if (handler.isValidHome()) { + directory = handler.getHome().getAbsolutePath(); + if (directory.length() > 2) { + directory = directory.substring(0, 2); + } + } else { + directory = "C:"; + } + defaultDirectory = makeJythonSubDirectory(directory); + } + // 2st try: user.home + if (defaultDirectory == null) { + directory = System.getProperty("user.home", ""); + if (directory.length() > 0) { + defaultDirectory = makeJythonSubDirectory(directory); + } + } + // 3rd try: user.dir + if (defaultDirectory == null) { + directory = System.getProperty("user.dir", ""); + if (directory.length() > 0) { + defaultDirectory = makeJythonSubDirectory(directory); + } + } + // 4th try: current directory + if (defaultDirectory == null) { + defaultDirectory = makeJythonSubDirectory(new File(new File("dummy").getAbsolutePath()).getParent()); + } + return defaultDirectory; + } + + private File makeJythonSubDirectory(String directory) { + File defaultDirectory = null; + File parentDirectory = new File(directory); + if (parentDirectory.exists() && parentDirectory.isDirectory()) { + String jythonSubDirectoryName = "jython" + (_jarInfo.getVersion()).replaceAll("\\+", ""); + defaultDirectory = new File(parentDirectory, jythonSubDirectoryName); + } + return defaultDirectory; + } + + private class BrowseButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String directoryName = _directory.getText(); + File directory = new File(directoryName); + if (directory.exists()) { + if (!directory.isDirectory()) { + // switch to parent directory if user typed the name of a file + directory = directory.getParentFile(); + } + } + JFileChooser fileChooser = new JFileChooser(directory); + fileChooser.setDialogTitle(getText(SELECT_TARGET_DIRECTORY)); + // the filter is at the moment only used for the title of the dialog: + fileChooser.setFileFilter(new DirectoryFilter()); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (fileChooser.isAcceptAllFileFilterUsed()) { + if (Installation.isMacintosh() && Installation.isJDK141()) { + // work around ArrayIndexOutOfBoundsExceptio on Mac OS X, java version 1.4.1 + } else { + fileChooser.setAcceptAllFileFilterUsed(false); + } + } + int returnValue = fileChooser.showDialog(_browse, getText(SELECT)); + if (returnValue == JFileChooser.APPROVE_OPTION) { + _directory.setText(fileChooser.getSelectedFile().getAbsolutePath()); + _directory.setToolTipText(_directory.getText()); + FrameInstaller.setTargetDirectory(_directory.getText()); + } + } + } + + private class DirectoryFocusListener implements FocusListener { + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + FrameInstaller.setTargetDirectory(_directory.getText()); + _directory.setToolTipText(_directory.getText()); + } + } + +} diff --git a/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java b/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java @@ -0,0 +1,36 @@ +package org.python.util.install; + +import java.io.File; + +public class DirectorySelectionPageValidator extends AbstractWizardValidator { + + DirectorySelectionPage _page; + + DirectorySelectionPageValidator(DirectorySelectionPage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException, ValidationInformationException { + String directory = _page.getDirectory().getText().trim(); // trim to be sure + if (directory != null && directory.length() > 0) { + File targetDirectory = new File(directory); + if (targetDirectory.exists()) { + if (targetDirectory.isDirectory()) { + if (targetDirectory.list().length > 0) { + throw new ValidationException(getText(NON_EMPTY_TARGET_DIRECTORY)); + } + } + } else { + if (targetDirectory.mkdirs()) { + throw new ValidationInformationException(Installation.getText(CREATED_DIRECTORY, directory)); + } else { + throw new ValidationException(getText(UNABLE_CREATE_DIRECTORY, directory)); + } + } + } else { + throw new ValidationException(getText(EMPTY_TARGET_DIRECTORY)); + } + FrameInstaller.setTargetDirectory(directory); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/EmptyValidator.java b/installer/src/java/org/python/util/install/EmptyValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/EmptyValidator.java @@ -0,0 +1,8 @@ +package org.python.util.install; + +public class EmptyValidator extends AbstractWizardValidator { + + protected void validate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/FileHelper.java b/installer/src/java/org/python/util/install/FileHelper.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/FileHelper.java @@ -0,0 +1,208 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * Helper methods for file handling during installation / installation verification + */ +public final class FileHelper { + + private final static String EXECUTABLE_MODE = "755"; + + /** + * create a temporary directory with the same name as the passed in File (which may exist as + * file, not directory) + * + * @param tempDirectory + * @return true only if the the directory was successfully created (or already + * existed) + */ + public static boolean createTempDirectory(File tempDirectory) { + boolean success = true; + if (!tempDirectory.isDirectory()) { + if (tempDirectory.exists()) { + success = carryOnResult(tempDirectory.delete(), success); + } + if (success) { + success = tempDirectory.mkdirs(); + } + } + return success; + } + + /** + * completely remove a directory + * + * @param dir + * @return true if successful, false otherwise. + */ + public static boolean rmdir(File dir) { + boolean success = true; + if (dir.exists()) { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isFile()) { + success = carryOnResult(file.delete(), success); + } else { + if (file.isDirectory()) { + success = carryOnResult(rmdir(file), success); + } + } + } + success = carryOnResult(dir.delete(), success); + } + return success; + } + + /** + * read the contents of a file into a String + * + * @param file + * The file has to exist + * @return The contents of the file as String + * @throws IOException + */ + public static String readAll(File file) throws IOException { + FileReader fileReader = new FileReader(file); + try { + StringBuffer sb = new StringBuffer(); + char[] b = new char[8192]; + int n; + while ((n = fileReader.read(b)) > 0) { + sb.append(b, 0, n); + } + return sb.toString(); + } finally { + fileReader.close(); + } + } + + /** + * read the contents of a stream into a String + *

+ * ATTN: does not handle encodings + * + * @param inputStream + * The input stream + * @return A String representation of the file contents + * @throws IOException + */ + public static String readAll(InputStream inputStream) throws IOException { + try { + StringBuffer sb = new StringBuffer(); + byte[] b = new byte[8192]; + int n; + while ((n = inputStream.read(b)) > 0) { + sb.append(new String(b, 0, n)); + } + return sb.toString(); + } finally { + inputStream.close(); + } + } + + /** + * Write contents to a file. + *

+ * An existing file would be overwritten. + * + * @param file + * @param contents + * + * @throws IOException + */ + public static void write(File file, String contents) throws IOException { + FileWriter writer = new FileWriter(file); + writer.write(contents); + writer.flush(); + writer.close(); + } + + /** + * determine the url of a file relative to (in the same directory as) the specified .class file
+ * can also be used if the .class file resides inside a .jar file + * + * @param clazz + * The class next to the file + * @param fileName + * The name of the file + * + * @return The url of the file, can be null + */ + public static URL getRelativeURL(Class clazz, String fileName) { + String filePath = getRelativePackagePath(clazz) + "/" + fileName; + return Thread.currentThread().getContextClassLoader().getResource(filePath); + } + + /** + * get the input stream of a file relative to (in the same directory as) the specified .class + * file
+ * can also be used if the .class file resides inside a .jar file + * + * @param clazz + * The class next to the file + * @param fileName + * The name of the file + * + * @return The input stream of the file, can be null + */ + public static InputStream getRelativeURLAsStream(Class clazz, String fileName) { + String filePath = getRelativePackagePath(clazz) + "/" + fileName; + return Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath); + } + + /** + * do a chmod on the passed file + * + * @param scriptFile + */ + public static void makeExecutable(File scriptFile) { + try { + String command[] = new String[3]; + command[0] = "chmod"; + command[1] = EXECUTABLE_MODE; + command[2] = scriptFile.getAbsolutePath(); + long timeout = 3000; + ChildProcess childProcess = new ChildProcess(command, timeout); + childProcess.run(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * build the package path for the class loader
+ * the class loader should be able to load a file appended to this path, if it is in the same + * directory. + * + * @param clazz + * The class + * + * @return The package path + */ + private static String getRelativePackagePath(Class clazz) { + String className = clazz.getName(); + String packageName = className.substring(0, className.lastIndexOf(".")); + return packageName.replace('.', '/'); + } + + /** + * @param newResult + * @param existingResult + * @return false if newResult or existingResult are false, true + * otherwise. + */ + private static boolean carryOnResult(boolean newResult, boolean existingResult) { + if (existingResult) { + return newResult; + } else { + return existingResult; + } + } +} diff --git a/installer/src/java/org/python/util/install/FrameInstaller.java b/installer/src/java/org/python/util/install/FrameInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/FrameInstaller.java @@ -0,0 +1,186 @@ +package org.python.util.install; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Locale; +import java.util.Properties; + +import javax.swing.UIManager; + +import org.python.util.install.Installation.JavaVersionInfo; +import org.python.util.install.driver.Autotest; + +public class FrameInstaller { + private static final String TRUE = "1"; + private static final String FALSE = "0"; + + private static final String JAVA_VERSION_PROPERTY = "FrameInstaller.Version"; + private static final String JAVA_VENDOR_PROPERTY = "FrameInstaller.Vendor"; + private static final String JAVA_SPEC_VERSION_PROPERTY = "FrameInstaller.SpecVersion"; + + private static final String INEX_MOD_PROPERTY = "FrameInstaller.mod"; + private static final String INEX_DEMO_PROPERTY = "FrameInstaller.demo"; + private static final String INEX_DOC_PROPERTY = "FrameInstaller.doc"; + private static final String INEX_SRC_PROPERTY = "FrameInstaller.src"; + private static final String STANDALONE_PROPERTY = "FrameInstaller.standalone"; + + private static Properties _properties = new Properties(); + + private static JavaHomeHandler _javaHomeHandler; + + protected FrameInstaller(InstallerCommandLine commandLine, JarInfo jarInfo, Autotest autotest) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + } + // clear all properties + _properties.clear(); + // set the default for the target directory + if (commandLine.hasDirectoryOption()) { + setTargetDirectory(commandLine.getTargetDirectory().getAbsolutePath()); + } + if (commandLine.hasJavaHomeOption()) { + setJavaHomeHandler(commandLine.getJavaHomeHandler()); + } + initDefaultJava(); + Wizard wizard = new Wizard(jarInfo, autotest); + wizard.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent event) { + if (!Installation.isAutotesting()) { + System.exit(0); + } + } + }); + wizard.addWizardListener(new SimpleWizardListener()); + wizard.setVisible(true); + } + + protected static void setProperty(String key, String value) { + _properties.setProperty(key, value); + } + + protected static String getProperty(String key) { + return _properties.getProperty(key); + } + + protected static String getProperty(String key, String defaultValue) { + return _properties.getProperty(key, defaultValue); + } + + protected static void setTargetDirectory(String targetDirectory) { + setProperty(TextKeys.TARGET_DIRECTORY_PROPERTY, targetDirectory.trim()); + } + + protected static String getTargetDirectory() { + return getProperty(TextKeys.TARGET_DIRECTORY_PROPERTY); + } + + protected static void setJavaHomeHandler(JavaHomeHandler javaHomeHandler) { + _javaHomeHandler = javaHomeHandler; + } + + protected static JavaHomeHandler getJavaHomeHandler() { + if (_javaHomeHandler == null) { + _javaHomeHandler = new JavaHomeHandler(); + } + return _javaHomeHandler; + } + + protected static void setLanguage(Locale locale) { + setProperty(TextKeys.LANGUAGE_PROPERTY, locale.toString()); + Installation.setLanguage(locale); + } + + protected static Locale getLanguage() { + return new Locale(getProperty(TextKeys.LANGUAGE_PROPERTY)); + } + + protected static InstallationType getInstallationType() { + InstallationType installationType = new InstallationType(); + if (Boolean.valueOf(getProperty(STANDALONE_PROPERTY)).booleanValue()) { + installationType.setStandalone(); + } + if (Boolean.valueOf(getProperty(INEX_MOD_PROPERTY)).booleanValue()) { + installationType.addLibraryModules(); + } else { + installationType.removeLibraryModules(); + } + if (Boolean.valueOf(getProperty(INEX_DEMO_PROPERTY)).booleanValue()) { + installationType.addDemosAndExamples(); + } else { + installationType.removeDemosAndExamples(); + } + if (Boolean.valueOf(getProperty(INEX_DOC_PROPERTY)).booleanValue()) { + installationType.addDocumentation(); + } else { + installationType.removeDocumentation(); + } + if (Boolean.valueOf(getProperty(INEX_SRC_PROPERTY)).booleanValue()) { + installationType.addSources(); + } else { + installationType.removeSources(); + } + return installationType; + } + + protected static void setInstallationType(InstallationType installationType) { + setProperty(STANDALONE_PROPERTY, Boolean.toString(installationType.isStandalone())); + setProperty(INEX_MOD_PROPERTY, Boolean.toString(installationType.installLibraryModules())); + setProperty(INEX_DEMO_PROPERTY, Boolean.toString(installationType.installDemosAndExamples())); + setProperty(INEX_DOC_PROPERTY, Boolean.toString(installationType.installDocumentation())); + setProperty(INEX_SRC_PROPERTY, Boolean.toString(installationType.installSources())); + } + + protected static JavaVersionInfo getJavaVersionInfo() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + javaVersionInfo.setVersion(getProperty(JAVA_VERSION_PROPERTY)); + javaVersionInfo.setVendor(getProperty(JAVA_VENDOR_PROPERTY)); + javaVersionInfo.setSpecificationVersion(getProperty(JAVA_SPEC_VERSION_PROPERTY)); + return javaVersionInfo; + } + + protected static void setJavaVersionInfo(JavaVersionInfo javaVersionInfo) { + setProperty(JAVA_VERSION_PROPERTY, javaVersionInfo.getVersion()); + setProperty(JAVA_VENDOR_PROPERTY, javaVersionInfo.getVendor()); + setProperty(JAVA_SPEC_VERSION_PROPERTY, javaVersionInfo.getSpecificationVersion()); + } + + protected static void setAccept(boolean accept) { + if (accept) { + setProperty(TextKeys.ACCEPT_PROPERTY, TRUE); + } else { + setProperty(TextKeys.ACCEPT_PROPERTY, FALSE); + } + } + + protected static boolean isAccept() { + return TRUE.equals(getProperty(TextKeys.ACCEPT_PROPERTY, FALSE)); + } + + protected static void initDefaultJava() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); + FrameInstaller.setJavaVersionInfo(javaVersionInfo); + } + + private class SimpleWizardListener implements WizardListener { + public void wizardStarted(WizardEvent event) { + } + + public void wizardFinished(WizardEvent event) { + if (!Installation.isAutotesting()) { + System.exit(0); + } + } + + public void wizardCancelled(WizardEvent event) { + System.exit(1); + } + + public void wizardNext(WizardEvent event) { + } + + public void wizardPrevious(WizardEvent event) { + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/Installation.java b/installer/src/java/org/python/util/install/Installation.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/Installation.java @@ -0,0 +1,439 @@ +package org.python.util.install; + +import java.awt.GraphicsEnvironment; // should be allowed on headless systems +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +import org.python.util.install.driver.Autotest; +import org.python.util.install.driver.InstallationDriver; +import org.python.util.install.driver.Tunnel; + +public class Installation { + public final static int NORMAL_RETURN = 0; + public final static int ERROR_RETURN = 1; + + protected static final String ALL = "1"; + protected static final String STANDARD = "2"; + protected static final String MINIMUM = "3"; + protected static final String STANDALONE = "9"; + + protected static final String OS_NAME = "os.name"; + protected static final String OS_VERSION = "os.version"; + protected static final String JAVA_VM_NAME = "java.vm.name"; + protected static final String EMPTY = ""; + + protected static final String HEADLESS_PROPERTY_NAME = "java.awt.headless"; + + private static final String RESOURCE_CLASS = "org.python.util.install.TextConstants"; + + private static ResourceBundle _textConstants = ResourceBundle.getBundle(RESOURCE_CLASS, Locale.getDefault()); + + private static boolean _verbose = false; + private static boolean _isAutotesting = false; + + public static void main(String args[]) { + internalMain(args, null, null); + } + + public static void driverMain(String args[], Autotest autotest, Tunnel tunnel) { + internalMain(args, autotest, tunnel); + } + + protected static boolean isVerbose() { + return _verbose; + } + + protected static void setVerbose(boolean verbose) { + _verbose = verbose; + } + + protected static boolean isAutotesting() { + return _isAutotesting; + } + + protected static String getText(String key) { + return _textConstants.getString(key); + } + + protected static String getText(String key, String... parameters) { + return MessageFormat.format(_textConstants.getString(key), (Object[])parameters); + } + + protected static void setLanguage(Locale locale) { + _textConstants = ResourceBundle.getBundle(RESOURCE_CLASS, locale); + } + + public static boolean isValidOs() { + String osName = System.getProperty(OS_NAME, ""); + String lowerOs = osName.toLowerCase(); + if (isWindows()) { + return true; + } + if (lowerOs.indexOf("linux") >= 0) { + return true; + } + if (lowerOs.indexOf("mac") >= 0) { + return true; + } + if (lowerOs.indexOf("unix") >= 0) { + return true; + } + return false; + } + + protected static boolean isValidJava(JavaVersionInfo javaVersionInfo) { + String specificationVersion = javaVersionInfo.getSpecificationVersion(); + verboseOutput("specification version: '" + specificationVersion + "'"); + boolean valid = true; + if (getJavaSpecificationVersion(specificationVersion) < 15) { + valid = false; + } + return valid; + } + + /** + * @return specification version as an int, e.g. 15 or 16 (the micro part is ignored) + * @param specificationVersion + * as system property + */ + public static int getJavaSpecificationVersion(String specificationVersion) { + // major.minor.micro + // according to http://java.sun.com/j2se/1.5.0/docs/guide/versioning/spec/versioning2.html + String major = "1"; + String minor = "0"; + StringTokenizer tokenizer = new StringTokenizer(specificationVersion, "."); + if (tokenizer.hasMoreTokens()) { + major = tokenizer.nextToken(); + } + if (tokenizer.hasMoreTokens()) { + minor = tokenizer.nextToken(); + } + return Integer.valueOf(major.concat(minor)).intValue(); + } + + public static boolean isWindows() { + boolean isWindows = false; + String osName = System.getProperty(OS_NAME, ""); + if (osName.toLowerCase().indexOf("windows") >= 0) { + isWindows = true; + } + return isWindows; + } + + protected static boolean isMacintosh() { + boolean isMacintosh = false; + String osName = System.getProperty(OS_NAME, ""); + if (osName.toLowerCase().indexOf("mac") >= 0) { + isMacintosh = true; + } + return isMacintosh; + } + + protected static boolean isGNUJava() { + boolean isGNUJava = false; + String javaVmName = System.getProperty(JAVA_VM_NAME, ""); + String lowerVmName = javaVmName.toLowerCase(); + if (lowerVmName.indexOf("gnu") >= 0 && lowerVmName.indexOf("libgcj") >= 0) { + isGNUJava = true; + } + return isGNUJava; + } + + protected static boolean isJDK141() { + boolean isJDK141 = false; + String javaVersion = System.getProperty(JavaVersionTester.JAVA_VERSION, ""); + if (javaVersion.toLowerCase().startsWith("1.4.1")) { + isJDK141 = true; + } + return isJDK141; + } + + /** + * Get the version info of an external (maybe other) jvm. + * + * @param javaHomeHandler + * The java home handler pointing to the java home of the external jvm.
+ * The /bin directory is assumed to be a direct child directory. + * + * @return The versionInfo + */ + protected static JavaVersionInfo getExternalJavaVersion(JavaHomeHandler javaHomeHandler) { + JavaVersionInfo versionInfo = new JavaVersionInfo(); + if (javaHomeHandler.isValidHome()) { + try { + ConsoleInstaller.message(getText(TextKeys.C_CHECK_JAVA_VERSION)); + // launch the java command - temporary file will be written by the child process + File tempFile = File.createTempFile("jython_installation", ".properties"); + if (tempFile.exists() && tempFile.canWrite()) { + String command[] = new String[5]; + command[0] = javaHomeHandler.getExecutableName(); + command[1] = "-cp"; + // our own class path should be ok here + command[2] = System.getProperty("java.class.path"); + command[3] = JavaVersionTester.class.getName(); + command[4] = tempFile.getAbsolutePath(); + verboseOutput("executing: " + command[0] + " " + command[1] + " " + command[2] + + " " + command[3] + " " + command[4]); + ChildProcess childProcess = new ChildProcess(command, 10000); // 10 seconds + childProcess.setDebug(Installation.isVerbose()); + int errorCode = childProcess.run(); + if (errorCode != NORMAL_RETURN) { + versionInfo.setErrorCode(errorCode); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } else { + Properties tempProperties = new Properties(); + tempProperties.load(new FileInputStream(tempFile)); + fillJavaVersionInfo(versionInfo, tempProperties); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_UNABLE_CREATE_TMPFILE, tempFile.getAbsolutePath())); + } + } catch (IOException e) { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } + + return versionInfo; + } + + /** + * @return The system default java version + */ + public static JavaVersionInfo getDefaultJavaVersion() { + JavaVersionInfo versionInfo = new JavaVersionInfo(); + String executableName = "java"; + try { + // launch the java command - temporary file will be written by the child process + File tempFile = File.createTempFile("jython_installation", ".properties"); + if (tempFile.exists() && tempFile.canWrite()) { + String command[] = new String[5]; + command[0] = executableName; + command[1] = "-cp"; + // our own class path should be ok here + command[2] = System.getProperty("java.class.path"); + command[3] = JavaVersionTester.class.getName(); + command[4] = tempFile.getAbsolutePath(); + ChildProcess childProcess = new ChildProcess(command, 10000); // 10 seconds + childProcess.setDebug(false); + int errorCode = childProcess.run(); + if (errorCode != NORMAL_RETURN) { + versionInfo.setErrorCode(errorCode); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, executableName)); + } else { + Properties tempProperties = new Properties(); + tempProperties.load(new FileInputStream(tempFile)); + fillJavaVersionInfo(versionInfo, tempProperties); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_UNABLE_CREATE_TMPFILE, + tempFile.getAbsolutePath())); + } + } catch (IOException e) { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, executableName)); + } + return versionInfo; + } + + protected static void fillJavaVersionInfo(JavaVersionInfo versionInfo, Properties properties) { + versionInfo.setVersion(properties.getProperty(JavaVersionTester.JAVA_VERSION)); + versionInfo.setSpecificationVersion(properties.getProperty(JavaVersionTester.JAVA_SPECIFICATION_VERSION)); + versionInfo.setVendor(properties.getProperty(JavaVersionTester.JAVA_VENDOR)); + } + + public static class JavaVersionInfo { + private String _version; + private String _specificationVersion; + private String _vendor; + private int _errorCode; + private String _reason; + + protected JavaVersionInfo() { + _version = EMPTY; + _specificationVersion = EMPTY; + _errorCode = NORMAL_RETURN; + _reason = EMPTY; + } + + protected void setVersion(String version) { + _version = version; + } + + protected void setSpecificationVersion(String specificationVersion) { + _specificationVersion = specificationVersion; + } + + protected void setVendor(String vendor) { + _vendor = vendor; + } + + protected void setErrorCode(int errorCode) { + _errorCode = errorCode; + } + + protected void setReason(String reason) { + _reason = reason; + } + + protected String getVersion() { + return _version; + } + + public String getSpecificationVersion() { + return _specificationVersion; + } + + protected String getVendor() { + return _vendor; + } + + public int getErrorCode() { + return _errorCode; + } + + protected String getReason() { + return _reason; + } + } + + protected static class JavaFilenameFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + if (name.toLowerCase().startsWith("java")) { + return true; + } else { + return false; + } + } + } + + public static boolean isGuiAllowed() { + verboseOutput("checking gui availability"); + if (Boolean.getBoolean(HEADLESS_PROPERTY_NAME)) { + verboseOutput(HEADLESS_PROPERTY_NAME + " is true"); + return false; + } else if (GraphicsEnvironment.isHeadless()) { + verboseOutput("GraphicsEnvironment is headless"); + return false; + } else { + try { + verboseOutput("trying to get the GraphicsEnvironment"); + GraphicsEnvironment.getLocalGraphicsEnvironment(); + verboseOutput("got the GraphicsEnvironment!"); + return true; + } catch (Throwable t) { + verboseOutput("got the following exception:"); + verboseOutput(t); + return false; + } + } + } + + // + // private methods + // + + private static boolean useGui(InstallerCommandLine commandLine) { + if (commandLine.hasConsoleOption() || commandLine.hasSilentOption()) { + return false; + } + return isGuiAllowed(); + } + + /** + * In the normal case, this method is called with (args, null, null), see main(args). + *

+ * However, in autotesting mode (commandLine.hasAutotestOption()), we pass in Autotest and Tunnel, + * see driverMain(args, autotest, tunnel). + *

+ * This means that in autotesting mode this method will call itself (via InstallationDriver.drive()), + * but with different arguments. + */ + private static void internalMain(String[] args, Autotest autotest, Tunnel tunnel) { + try { + setVerbose(InstallerCommandLine.hasVerboseOptionInArgs(args)); + dumpSystemProperties(); + verboseOutput("reading jar info"); + JarInfo jarInfo = new JarInfo(); + InstallerCommandLine commandLine = new InstallerCommandLine(jarInfo); + if (!commandLine.setArgs(args) || commandLine.hasHelpOption()) { + commandLine.printHelp(); + System.exit(1); + } else { + if (commandLine.hasAutotestOption()) { + verboseOutput("running autotests"); + _isAutotesting = true; + InstallationDriver autotestDriver = new InstallationDriver(commandLine); + autotestDriver.drive(); // ! reentrant into internalMain() + _isAutotesting = false; + ConsoleInstaller.message("\ncongratulations - autotests complete !"); + System.exit(0); + } + if (!useGui(commandLine)) { + verboseOutput("using the console installer"); + ConsoleInstaller consoleInstaller = new ConsoleInstaller(commandLine, jarInfo); + consoleInstaller.setTunnel(tunnel); + consoleInstaller.install(); + if (!isAutotesting()) { + System.exit(0); + } + } else { + verboseOutput("using the gui installer"); + new FrameInstaller(commandLine, jarInfo, autotest); + } + } + } catch (InstallationCancelledException ice) { + ConsoleInstaller.message((getText(TextKeys.INSTALLATION_CANCELLED))); + System.exit(1); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } + + private static void dumpSystemProperties() throws IOException { + if (isVerbose()) { + @SuppressWarnings("unchecked") + Enumeration names = (Enumeration)System.getProperties().propertyNames(); + StringBuilder contents = new StringBuilder(400); + contents.append("Properties at the beginning of the Jython installation:\n\n"); + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = System.getProperty(name, ""); + contents.append(name); + contents.append('='); + contents.append(value); + contents.append("\n"); + } + File output = File.createTempFile("System", ".properties"); + FileHelper.write(output, contents.toString()); + ConsoleInstaller.message("system properties dumped to " + output.getAbsolutePath()); + } + } + + private static void verboseOutput(String message) { + if (isVerbose()) { + ConsoleInstaller.message(message); + } + } + + private static void verboseOutput(Throwable t) { + if (isVerbose()) { + t.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/InstallationCancelledException.java b/installer/src/java/org/python/util/install/InstallationCancelledException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationCancelledException.java @@ -0,0 +1,9 @@ +package org.python.util.install; + +public class InstallationCancelledException extends InstallerException { + + public InstallationCancelledException() { + super(); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/InstallationListener.java b/installer/src/java/org/python/util/install/InstallationListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationListener.java @@ -0,0 +1,7 @@ +package org.python.util.install; + +public interface InstallationListener { + + public void progressFinished(); + +} diff --git a/installer/src/java/org/python/util/install/InstallationType.java b/installer/src/java/org/python/util/install/InstallationType.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationType.java @@ -0,0 +1,120 @@ +package org.python.util.install; + +public class InstallationType { + + private boolean _installLibraryModules = true; + private boolean _installDemosAndExamples = true; + private boolean _installDocumentation = true; + private boolean _installSources = false; + private boolean _isStandalone = false; + + public boolean installLibraryModules() { + return _installLibraryModules; + } + + public boolean installDemosAndExamples() { + return _installDemosAndExamples; + } + + public boolean installDocumentation() { + return _installDocumentation; + } + + public boolean installSources() { + return _installSources; + } + + public void addLibraryModules() { + _installLibraryModules = true; + } + + public void removeLibraryModules() { + _installLibraryModules = false; + } + + public void addDemosAndExamples() { + _installDemosAndExamples = true; + } + + public void removeDemosAndExamples() { + _installDemosAndExamples = false; + } + + public void addDocumentation() { + _installDocumentation = true; + } + + public void removeDocumentation() { + _installDocumentation = false; + } + + public void addSources() { + _installSources = true; + } + + public void removeSources() { + _installSources = false; + } + + public void setStandalone() { + _isStandalone = true; + addLibraryModules(); + removeDemosAndExamples(); + removeDocumentation(); + removeSources(); + } + + public boolean isStandalone() { + return _isStandalone; + } + + public void setAll() { + addLibraryModules(); + addDemosAndExamples(); + addDocumentation(); + addSources(); + _isStandalone = false; + } + + public boolean isAll() { + return installLibraryModules() && installDemosAndExamples() && installDocumentation() && installSources(); + } + + public void setStandard() { + addLibraryModules(); + addDemosAndExamples(); + addDocumentation(); + removeSources(); + _isStandalone = false; + } + + public boolean isStandard() { + return installLibraryModules() && installDemosAndExamples() && installDocumentation() && !installSources(); + } + + public void setMinimum() { + removeLibraryModules(); + removeDemosAndExamples(); + removeDocumentation(); + removeSources(); + _isStandalone = false; + } + + public boolean isMinimum() { + return !installLibraryModules() && !installDemosAndExamples() && !installDocumentation() && !installSources(); + } + + /** + * @return true if current settings reflect one of the predefined settings + */ + public boolean isPredefined() { + return isAll() || isStandard() || isMinimum() || isStandalone(); + } + + public String toString() { + StringBuffer buf = new StringBuffer(30); + buf.append("mod: " + installDemosAndExamples() + ", demo: " + installDemosAndExamples() + ", doc: " + + installDocumentation() + ", src: " + installSources()); + return buf.toString(); + } +} diff --git a/installer/src/java/org/python/util/install/InstallerCommandLine.java b/installer/src/java/org/python/util/install/InstallerCommandLine.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallerCommandLine.java @@ -0,0 +1,456 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.MissingArgumentException; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.Parser; +import org.apache.commons.cli.PosixParser; + +public class InstallerCommandLine { + protected static final String INEXCLUDE_LIBRARY_MODULES = "mod"; + protected static final String INEXCLUDE_DEMOS_AND_EXAMPLES = "demo"; + protected static final String INEXCLUDE_DOCUMENTATION = "doc"; + protected static final String INEXCLUDE_SOURCES = "src"; + + protected static final String CONSOLE_SHORT = "c"; + protected static final String CONSOLE_LONG = "console"; + private static final String CONSOLE_DESC = "console based installation (user interaction)\n" + + "any other options will be ignored (except 'verbose')"; + + protected static final String SILENT_SHORT = "s"; + protected static final String SILENT_LONG = "silent"; + private static final String SILENT_DESC = "silent installation (without user interaction)"; + + protected static final String VERBOSE_SHORT = "v"; + protected static final String VERBOSE_LONG = "verbose"; + private static final String VERBOSE_DESC = "print more output during the installation\n" + + "(also valid in GUI and autotest mode)"; + + private static final String JRE_SHORT = "j"; + private static final String JRE_LONG = "jre"; + private static final String JRE_DESC = "home directory of the runtime jre or jdk\n" + + "(executables are assumed in the /bin subdirectory)\n" + "select this if you want to run Jython with a\n" + + "different java version than the installation"; + + private static final String AUTOTEST_SHORT = "A"; + private static final String AUTOTEST_LONG = "autotest"; + private static final String AUTOTEST_DESC = "automatic stress tests for the installer\n" + + "most of the other options are ignored\n" + "allowed additional options: '" + VERBOSE_LONG + "', '" + + JRE_LONG + "'"; + + private static final String DIRECTORY_SHORT = "d"; + private static final String DIRECTORY_LONG = "directory"; + private static final String DIRECTORY_DESC = "target directory to install to\n" + + "(required in silent mode,\nused as default in GUI mode)"; + + private static final String DIRECTORY_ARG = "dir"; + + private static final String TYPE_STANDARD = "standard"; + private static final String TYPE_ALL = "all"; + private static final String TYPE_MINIMUM = "minimum"; + protected static final String TYPE_STANDALONE = "standalone"; + private static final String STANDALONE_DOCUMENTATION = "install a single, executable .jar,\ncontaining all the modules"; + + private static final String INEXCLUDE_ARG = "part(s)"; + private static final String INEXCLUDE_PARTS = "more than one of the following is possible:\n" + "- " + + INEXCLUDE_LIBRARY_MODULES + ": library modules\n" + "- " + INEXCLUDE_DEMOS_AND_EXAMPLES + + ": demos and examples\n" + "- " + INEXCLUDE_DOCUMENTATION + ": documentation\n" + "- " + + INEXCLUDE_SOURCES + ": java source code"; + + private static final String TYPE_SHORT = "t"; + private static final String TYPE_LONG = "type"; + private static final String TYPE_ARG = TYPE_LONG; + private static final String TYPE_DESC = "installation type\n" + "one of the following types is possible\n" + + "(see also include/exclude parts):\n" + "- " + TYPE_ALL + ": everything (including " + INEXCLUDE_SOURCES + + ")\n" + "- " + TYPE_STANDARD + ": core, " + INEXCLUDE_LIBRARY_MODULES + ", " + + INEXCLUDE_DEMOS_AND_EXAMPLES + ", " + INEXCLUDE_DOCUMENTATION + ",\n"+ TYPE_STANDARD+ " is the default\n" + "- " + TYPE_MINIMUM + ": core\n" + + "- " + TYPE_STANDALONE + ": " + STANDALONE_DOCUMENTATION; + + private static final String INCLUDE_SHORT = "i"; + private static final String INCLUDE_LONG = "include"; + private static final String INCLUDE_DESC = "finer control over parts to install\n" + INEXCLUDE_PARTS; + + private static final String EXCLUDE_SHORT = "e"; + private static final String EXCLUDE_LONG = "exclude"; + private static final String EXCLUDE_DESC = "finer control over parts not to install\n" + INEXCLUDE_PARTS + + "\n(excludes override includes)"; + + private static final String HELP_SHORT = "h"; + private static final String HELP2_SHORT = "?"; + private static final String HELP_LONG = "help"; + private static final String HELP_DESC = "print this help (overrides any other options)"; + + private static final String SYNTAX = "\n\tjava -jar jython_version.jar"; + private static final String HEADER = "\nNo option at all will start the interactive GUI installer, except:\n" + + "Options respected in GUI mode are '" + DIRECTORY_LONG + "' and '" + JRE_LONG + + "', which serve as default values in the wizard.\n" + + "In non-GUI mode the following options are available:\n."; + private static final String SYNTAX_WITHOUT_JAR = "\n\tjava -jar "; + private static final String FOOTER = ""; + private static final String EXAMPLES = "\nexample of a GUI installation:{0}" + + "\n\nexample of a console installation:{0} -" + CONSOLE_SHORT + + "\n\nexample of a silent installation:{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + " targetDirectory" + + "\n\nexamples of a silent installation with more options:{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + + " targetDirectory -" + TYPE_SHORT + " " + TYPE_MINIMUM + " -" + INCLUDE_SHORT + " " + INEXCLUDE_SOURCES + + " -" + JRE_SHORT + " javaHome" + "{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + " targetDirectory -" + + TYPE_SHORT + " " + TYPE_STANDARD + " -" + EXCLUDE_SHORT + " " + INEXCLUDE_DEMOS_AND_EXAMPLES + " " + + INEXCLUDE_DOCUMENTATION + "\n\t\t -" + INCLUDE_SHORT + " " + INEXCLUDE_SOURCES + " -" + JRE_SHORT + + " javaHome -" + VERBOSE_SHORT + + "\n\nexample of an autotest installation into temporary directories:{0} -" + AUTOTEST_SHORT + + "\n\t(make sure you do NOT touch mouse NOR keyboard after hitting enter/return!)" + + "\n\nexample of an autotest installation, using a different jre for the start scripts:{0} -" + + AUTOTEST_SHORT + " -" + JRE_SHORT + " javaHome" + " -" + VERBOSE_SHORT + + "\n\t(make sure you do NOT touch mouse NOR keyboard after hitting enter/return!)"; + + private String[] _args; + private Options _options; + private CommandLine _commandLine; + private JarInfo _jarInfo; + private final Parser _parser = new PosixParser(); + + public InstallerCommandLine(JarInfo jarInfo) { + createOptions(); + _jarInfo = jarInfo; + } + + /** + * Pre-scan of the arguments to detect a verbose flag + * @param args + * @return true if there is a verbose option + */ + public static final boolean hasVerboseOptionInArgs(String[] args) { + String shortVerbose = "-".concat(VERBOSE_SHORT); + String longVerbose = "--".concat(VERBOSE_LONG); + return hasOptionInArgs(args, shortVerbose, longVerbose); + } + + /** + * constructor intended for JUnit tests only. + */ + public InstallerCommandLine() { + this(null); + } + + /** + * Set the arguments from the command line. + * + * @param args the arguments of the command line + * @return true if all arguments are valid, false otherwise. No help is printed if + * false is returned + */ + public boolean setArgs(String args[]) { + // pre-process args to determine if we can (and should) switch to console mode + try { + CommandLine preCommandLine = _parser.parse(_options, args, false); + if (!hasConsoleOption(preCommandLine) && !hasSilentOption(preCommandLine) + && !hasAutotestOption(preCommandLine)) { + if (!Installation.isGuiAllowed() || Installation.isGNUJava()) { + // auto switch to console mode + if (hasVerboseOption(preCommandLine)) { + ConsoleInstaller.message("auto-switching to console mode"); + } + String[] newArgs = new String[args.length + 1]; + System.arraycopy(args, 0, newArgs, 0, args.length); + newArgs[args.length] = "-" + CONSOLE_SHORT; + args = newArgs; + } + } + } catch (Exception e) { + // ignore + } + _args = args; + try { + // throws for missing or unknown options / arguments + _commandLine = _parser.parse(_options, _args, false); + } catch (MissingArgumentException mae) { + System.err.println(mae.getMessage()); + return false; + } catch (ParseException pe) { + System.err.println(pe.getMessage()); + return false; + } + List unrecognized = _commandLine.getArgList(); + if (unrecognized.size() > 0) { + System.err.println("unrecognized argument(s): " + unrecognized); + return false; + } + if (hasTypeOption()) { + String type = _commandLine.getOptionValue(TYPE_SHORT); + if (TYPE_ALL.equals(type) || TYPE_STANDARD.equals(type) || TYPE_MINIMUM.equals(type) + || TYPE_STANDALONE.equals(type)) { + } else { + System.err.println("unrecognized argument '" + type + "' to option: " + TYPE_SHORT + " / " + TYPE_LONG); + return false; + } + } + if (hasSilentOption()) { + if (!hasDirectoryOption()) { + System.err.println("option " + DIRECTORY_SHORT + " / " + DIRECTORY_LONG + " is required in " + + SILENT_LONG + " mode"); + return false; + } + } + if (hasIncludeOption()) { + String[] includeParts = _commandLine.getOptionValues(INCLUDE_SHORT); + for (int i = 0; i < includeParts.length; i++) { + if (!isValidInExcludePart(includeParts[i])) { + System.err.println("unrecognized include part '" + includeParts[i] + "'"); + return false; + } + } + } + if (hasExcludeOption()) { + String[] excludeParts = _commandLine.getOptionValues(EXCLUDE_SHORT); + for (int i = 0; i < excludeParts.length; i++) { + if (!isValidInExcludePart(excludeParts[i])) { + System.err.println("unrecognized exclude part '" + excludeParts[i] + "'"); + return false; + } + } + } + return true; + } + + public boolean hasArguments() { + return _args.length > 0; + } + + public boolean hasHelpOption() { + return _commandLine.hasOption(HELP_SHORT) || _commandLine.hasOption(HELP2_SHORT) + || _commandLine.hasOption(HELP_LONG); + } + + public boolean hasSilentOption() { + return hasSilentOption(_commandLine); + } + + private boolean hasSilentOption(CommandLine commandLine) { + return commandLine.hasOption(SILENT_SHORT) || commandLine.hasOption(SILENT_LONG); + } + + public boolean hasConsoleOption() { + return hasConsoleOption(_commandLine); + } + + private boolean hasConsoleOption(CommandLine commandLine) { + return commandLine.hasOption(CONSOLE_SHORT) || commandLine.hasOption(CONSOLE_LONG); + } + + public boolean hasAutotestOption() { + return hasAutotestOption(_commandLine); + } + + private boolean hasAutotestOption(CommandLine commandLine) { + return commandLine.hasOption(AUTOTEST_SHORT) || commandLine.hasOption(AUTOTEST_LONG); + } + + public boolean hasDirectoryOption() { + return _commandLine.hasOption(DIRECTORY_SHORT) || _commandLine.hasOption(DIRECTORY_LONG); + } + + public boolean hasTypeOption() { + return _commandLine.hasOption(TYPE_SHORT) || _commandLine.hasOption(TYPE_LONG); + } + + public boolean hasIncludeOption() { + return _commandLine.hasOption(INCLUDE_SHORT) || _commandLine.hasOption(INCLUDE_LONG); + } + + public boolean hasExcludeOption() { + return _commandLine.hasOption(EXCLUDE_SHORT) || _commandLine.hasOption(EXCLUDE_LONG); + } + + public boolean hasJavaHomeOption() { + return _commandLine.hasOption(JRE_SHORT) || _commandLine.hasOption(JRE_LONG); + } + + public boolean hasVerboseOption() { + return hasVerboseOption(_commandLine); + } + + private boolean hasVerboseOption(CommandLine commandLine) { + return commandLine.hasOption(VERBOSE_SHORT) || commandLine.hasOption(VERBOSE_LONG); + } + + public void printHelp() { + HelpFormatter formatter = new HelpFormatter(); + formatter.defaultWidth = 76; + String syntax = SYNTAX; + if (_jarInfo != null) { + try { + syntax = SYNTAX_WITHOUT_JAR + _jarInfo.getJarFile().getName(); + } catch (IOException ioe) { + } + } + formatter.printHelp(syntax, HEADER, _options, FOOTER, true); + String examples = MessageFormat.format(EXAMPLES, syntax); + System.out.println(examples); + } + + /** + * @return the requested target directory, null if no directory specified + */ + public File getTargetDirectory() { + if (hasDirectoryOption()) { + return new File(_commandLine.getOptionValue(DIRECTORY_SHORT)); + } else { + return null; + } + } + + /** + * @return a java home handler for the requested java home directory, or a default handler if no + * java home specified + */ + public JavaHomeHandler getJavaHomeHandler() { + if (hasJavaHomeOption()) { + return new JavaHomeHandler(_commandLine.getOptionValue(JRE_SHORT)); + } else { + return new JavaHomeHandler(); + } + } + + /** + * The Installation type is built out of the type, include and exclude option + * + * @return the installation type usable for the jar installer + */ + public InstallationType getInstallationType() { + InstallationType installationType = new InstallationType(); // defaults to standard + // build a priori values out of the type option + if (hasTypeOption()) { + String typeName = _commandLine.getOptionValue(TYPE_SHORT); + if (TYPE_ALL.equals(typeName)) { + installationType.setAll(); + } else if (TYPE_MINIMUM.equals(typeName)) { + installationType.setMinimum(); + } else if (TYPE_STANDALONE.equals(typeName)) { + installationType.setStandalone(); + } + } + // add parts to include + if (hasIncludeOption()) { + String[] includeParts = _commandLine.getOptionValues(INCLUDE_SHORT); + for (int i = 0; i < includeParts.length; i++) { + if (INEXCLUDE_DEMOS_AND_EXAMPLES.equals(includeParts[i])) { + installationType.addDemosAndExamples(); + } + if (INEXCLUDE_DOCUMENTATION.equals(includeParts[i])) { + installationType.addDocumentation(); + } + if (INEXCLUDE_LIBRARY_MODULES.equals(includeParts[i])) { + installationType.addLibraryModules(); + } + if (INEXCLUDE_SOURCES.equals(includeParts[i])) { + installationType.addSources(); + } + } + } + // remove parts to exclude + if (hasExcludeOption()) { + String[] excludeParts = _commandLine.getOptionValues(EXCLUDE_SHORT); + for (int i = 0; i < excludeParts.length; i++) { + if (INEXCLUDE_DEMOS_AND_EXAMPLES.equals(excludeParts[i])) { + installationType.removeDemosAndExamples(); + } + if (INEXCLUDE_DOCUMENTATION.equals(excludeParts[i])) { + installationType.removeDocumentation(); + } + if (INEXCLUDE_LIBRARY_MODULES.equals(excludeParts[i])) { + installationType.removeLibraryModules(); + } + if (INEXCLUDE_SOURCES.equals(excludeParts[i])) { + installationType.removeSources(); + } + } + } + return installationType; + } + + // + // private methods + // + + private static final boolean hasOptionInArgs(String[] args, String shortOption, String longOption) { + boolean hasOption = false; + int i = 0; + while (!hasOption && i < args.length) { + if (shortOption.equals(args[i]) || longOption.equals(args[i])) { + hasOption = true; + } + i++; + } + return hasOption; + } + + private void createOptions() { + _options = new Options(); + _options.setSortAsAdded(true); + + // console or silent mode + Option consoleOption = new Option(CONSOLE_SHORT, CONSOLE_LONG, false, CONSOLE_DESC); + Option silentOption = new Option(SILENT_SHORT, SILENT_LONG, false, SILENT_DESC); + Option autotestOption = new Option(AUTOTEST_SHORT, AUTOTEST_LONG, false, AUTOTEST_DESC); + OptionGroup group1 = new OptionGroup(); + group1.addOption(consoleOption); + group1.addOption(silentOption); + group1.addOption(autotestOption); + _options.addOptionGroup(group1); + + // target directory + Option directoryOption = new Option(DIRECTORY_SHORT, DIRECTORY_LONG, true, DIRECTORY_DESC); + directoryOption.setArgName(DIRECTORY_ARG); + _options.addOption(directoryOption); + + // installation type + Option typeOption = new Option(TYPE_SHORT, TYPE_LONG, true, TYPE_DESC); + typeOption.setArgName(TYPE_ARG); + _options.addOption(typeOption); + + // additional parts to include + Option includeOption = new Option(INCLUDE_SHORT, INCLUDE_DESC); + includeOption.setArgs(4); + includeOption.setArgName(INEXCLUDE_ARG); + includeOption.setLongOpt(INCLUDE_LONG); + _options.addOption(includeOption); + + // parts to exclude + Option excludeOption = new Option(EXCLUDE_SHORT, EXCLUDE_DESC); + excludeOption.setArgs(4); + excludeOption.setArgName(INEXCLUDE_ARG); + excludeOption.setLongOpt(EXCLUDE_LONG); + _options.addOption(excludeOption); + + // runtime jre + Option jreOption = new Option(JRE_SHORT, JRE_LONG, true, JRE_DESC); + jreOption.setArgName(DIRECTORY_ARG); + _options.addOption(jreOption); + + // verbose + Option verboseOption = new Option(VERBOSE_SHORT, VERBOSE_LONG, false, VERBOSE_DESC); + _options.addOption(verboseOption); + + // different help options + Option helpHOption = new Option(HELP_SHORT, HELP_LONG, false, HELP_DESC); + Option helpQOption = new Option(HELP2_SHORT, HELP_DESC); + OptionGroup group2 = new OptionGroup(); + group2.addOption(helpHOption); + group2.addOption(helpQOption); + _options.addOptionGroup(group2); + } + + private boolean isValidInExcludePart(String part) { + return INEXCLUDE_DEMOS_AND_EXAMPLES.equals(part) || INEXCLUDE_DOCUMENTATION.equals(part) + || INEXCLUDE_LIBRARY_MODULES.equals(part) || INEXCLUDE_SOURCES.equals(part); + } + +} diff --git a/installer/src/java/org/python/util/install/InstallerException.java b/installer/src/java/org/python/util/install/InstallerException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallerException.java @@ -0,0 +1,21 @@ +package org.python.util.install; + +public class InstallerException extends RuntimeException { + + public InstallerException() { + super(); + } + + public InstallerException(String message) { + super(message); + } + + public InstallerException(String message, Throwable cause) { + super(message, cause); + } + + public InstallerException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JarInfo.java b/installer/src/java/org/python/util/install/JarInfo.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JarInfo.java @@ -0,0 +1,235 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class JarInfo { + private static final String JAR_URL_PREFIX = "jar:file:"; + private static final String JAR_SEPARATOR = "!"; + private static final String JYTHON = "Jython"; + private static final String VERSION_ATTRIBUTE = "version"; + private static final String EXCLUDE_DIRS_ATTRIBUTE = "exclude-dirs"; + private static final String EXCLUDE_DIRS_DELIM = ";"; + + private File _jarFile; + private int _numberOfEntries; + private Manifest _manifest; + private String _licenseText; + private String _readmeText; + + public JarInfo() { + _jarFile = null; + _numberOfEntries = 0; + _manifest = null; + + try { + readJarInfo(); + } catch (IOException ioe) { + throw new InstallerException(Installation.getText(TextKeys.ERROR_ACCESS_JARFILE), ioe); + } + } + + public String getVersion() { + String version = ""; + try { + Attributes jythonAttributes = getManifest().getAttributes(JYTHON); + if (jythonAttributes != null) { + version = jythonAttributes.getValue(VERSION_ATTRIBUTE); // do + // not + // use + // containsKey + } + } catch (IOException ioe) { + } + return version; + } + + public File getJarFile() throws IOException { + if (_jarFile == null) + readJarInfo(); + return _jarFile; + } + + public Manifest getManifest() throws IOException { + if (_manifest == null) + readJarInfo(); + return _manifest; + } + + public int getNumberOfEntries() throws IOException { + if (_numberOfEntries == 0) + readJarInfo(); + return _numberOfEntries; + } + + public List getExcludeDirs() throws IOException { + List excludeDirs = new ArrayList(); + Attributes jythonAttributes = getManifest().getAttributes(JYTHON); + if (jythonAttributes != null) { + // do not use containsKey + String excludeDirsString = jythonAttributes.getValue(EXCLUDE_DIRS_ATTRIBUTE); + if (excludeDirsString != null && excludeDirsString.length() > 0) { + StringTokenizer tokenizer = new StringTokenizer(excludeDirsString, EXCLUDE_DIRS_DELIM); + while (tokenizer.hasMoreTokens()) { + excludeDirs.add(tokenizer.nextToken()); + } + } + } + return excludeDirs; + } + + public String getLicenseText() throws IOException { + if (_licenseText == null) { + readJarInfo(); + } + return _licenseText; + } + + public String getReadmeText() throws IOException { + if (_readmeText == null) { + readJarInfo(); + } + return _readmeText; + } + + private void readJarInfo() throws IOException { + String fullClassName = getClass().getName(); + String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1); + URL url = getClass().getResource(className + ".class"); + // we expect an URL like: + // jar:file:/C:/stuff/jython21i.jar!/org/python/util/install/JarInfo.class + // escape plus signs, since the URLDecoder would turn them into spaces + final String plus = "\\+"; + final String escapedPlus = "__ppluss__"; + String rawUrl = url.toString(); + rawUrl = rawUrl.replaceAll(plus, escapedPlus); + String urlString = URLDecoder.decode(rawUrl, "UTF-8"); + urlString = urlString.replaceAll(escapedPlus, plus); + int jarSeparatorIndex = urlString.lastIndexOf(JAR_SEPARATOR); + if (!urlString.startsWith(JAR_URL_PREFIX) || jarSeparatorIndex <= 0) { + throw new InstallerException(Installation.getText(TextKeys.UNEXPECTED_URL, urlString)); + } + String jarFileName = urlString.substring(JAR_URL_PREFIX.length(), jarSeparatorIndex); + _jarFile = new File(jarFileName); + if (!_jarFile.exists()) { + throw new InstallerException(Installation.getText(TextKeys.JAR_NOT_FOUND, _jarFile.getAbsolutePath())); + } + JarFile jarFile = new JarFile(_jarFile); + Enumeration entries = jarFile.entries(); + _numberOfEntries = 0; + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + if ("LICENSE.txt".equals(entry.getName())) { + _licenseText = readTextFile(entry, jarFile); + } + if ("README.txt".equals(entry.getName())) { + _readmeText = readTextFile(entry, jarFile); + } + _numberOfEntries++; + } + _manifest = jarFile.getManifest(); + if (_manifest == null) { + throw new InstallerException(Installation.getText(TextKeys.NO_MANIFEST, _jarFile.getAbsolutePath())); + } + jarFile.close(); + } + + /** + * Read the text file with the most appropriate Charset. + * + * @param entry + * @param jarFile + * + * @return the contents of the text file + * + * @throws IOException + */ + private String readTextFile(JarEntry entry, JarFile jarFile) throws IOException { + String contents = readTextFileWithCharset(jarFile, entry, "US-ASCII"); // expected to run on most platforms + if (contents == null) { + contents = readTextFileWithCharset(jarFile, entry, "ISO-8859-1"); + } + if (contents == null) { + contents = readTextFileWithDefaultCharset(jarFile, entry); + } + return contents; + } + + /** + * Try to read the text file (jarEntry) from the jarFile, using a given charsetName. + * + * @param jarFile + * @param entry + * @param charsetName the name of the Charset + * + * @return the contents of the text file as String (if reading was successful), null otherwise.
+ * No exception is thrown + */ + private String readTextFileWithCharset(JarFile jarFile, JarEntry entry, String charsetName) { + String contents = null; + if (Charset.isSupported(charsetName)) { + BufferedReader reader = null; + try { + StringBuffer buffer = new StringBuffer(1000); + reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry), Charset + .forName(charsetName))); + buffer = new StringBuffer(1000); + for (String s; (s = reader.readLine()) != null;) { + buffer.append(s); + buffer.append("\n"); + } + contents = buffer.toString(); + } catch (IOException ioe) { + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + } + } + } + return contents; + } + + /** + * Read the text file (jarEntry) from the jarFile, using the platform default Charset. + * + * @param jarFile + * @param entry + * + * @return the contents of the text file as String. + * + * @throws IOException if a problem occurs + */ + private String readTextFileWithDefaultCharset(JarFile jarFile, JarEntry entry) throws IOException { + String contents = null; + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry))); + StringBuffer buffer = new StringBuffer(1000); + for (String s; (s = reader.readLine()) != null;) { + buffer.append(s); + buffer.append("\n"); + } + contents = buffer.toString(); + } finally { + if (reader != null) + reader.close(); + } + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JarInstaller.java b/installer/src/java/org/python/util/install/JarInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JarInstaller.java @@ -0,0 +1,283 @@ +package org.python.util.install; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Working horse extracting the contents of the installation .jar to the file system.
+ * The directory stucture is preserved, but there is the possibility to exclude some entries + * (directories at the moment). + */ +public class JarInstaller { + + public static final String JYTHON_JAR = "jython.jar"; + + private static final String PATH_SEPARATOR = "/"; + + private static final String LIB_NAME_SEP = "Lib" + PATH_SEPARATOR; + + private static final String LIB_PAWT_SEP = LIB_NAME_SEP + "pawt" + PATH_SEPARATOR; + + private static final int BUFFER_SIZE = 1024; + + private ProgressListener _progressListener; + + private JarInfo _jarInfo; + + private List _installationListeners; + + public JarInstaller(ProgressListener progressListener, JarInfo jarInfo) { + _progressListener = progressListener; + _jarInfo = jarInfo; + _installationListeners = new ArrayList(); + } + + /** + * Do the pysical installation: + *

    + *
  • unzip the files + *
  • generate the start scripts + *
+ * + * @param targetDirectory + * @param installationType + */ + public void inflate(final File targetDirectory, InstallationType installationType, JavaHomeHandler javaHomeHandler) { + try { + // has to correspond with build.xml + // has to correspond with build.Lib.include.properties + List excludeDirs = _jarInfo.getExcludeDirs(); + List coreLibFiles = new ArrayList(); + if (!installationType.installSources()) { + excludeDirs.add("src"); + excludeDirs.add("grammar"); + excludeDirs.add("extlibs"); + } + if (!installationType.installDocumentation()) { + excludeDirs.add("Doc"); + } + if (!installationType.installDemosAndExamples()) { + excludeDirs.add("Demo"); + } + if (!installationType.installLibraryModules()) { + excludeDirs.add(LIB_NAME_SEP + "email"); + excludeDirs.add(LIB_NAME_SEP + "encodings"); + excludeDirs.add(LIB_NAME_SEP + "test"); + excludeDirs.add(LIB_NAME_SEP + "jxxload_help"); + coreLibFiles = getCoreLibFiles(); + } + if (installationType.isStandalone()) { + excludeDirs.add("Tools"); + excludeDirs.add(LIB_NAME_SEP + "email/test"); + excludeDirs.add(LIB_NAME_SEP + "test"); + } + int count = 0; + int percent = 0; + int numberOfIntervals = 100 / _progressListener.getInterval(); + int numberOfEntries = approximateNumberOfEntries(installationType); + int threshold = numberOfEntries / numberOfIntervals + 1; // +1 = pessimistic + boolean coreExclusionReported = false; + // unzip + ZipInputStream zipInput = new ZipInputStream(new BufferedInputStream(new FileInputStream(_jarInfo.getJarFile()), + BUFFER_SIZE)); + ZipEntry zipEntry = zipInput.getNextEntry(); + while (zipEntry != null) { + String zipEntryName = zipEntry.getName(); + boolean exclude = false; + // handle exclusion of directories + Iterator excludeDirsAsIterator = excludeDirs.iterator(); + while (excludeDirsAsIterator.hasNext()) { + if (zipEntryName.startsWith(excludeDirsAsIterator.next() + + PATH_SEPARATOR)) { + exclude = true; + } + } + // exclude build.xml when not installing source + if (!installationType.installSources() && zipEntryName.equals("build.xml")) + exclude = true; + // handle exclusion of core Lib files + if (!exclude) { + exclude = shouldExcludeFile(installationType, + coreLibFiles, + zipEntry, + zipEntryName); + if (Installation.isVerbose() && !coreExclusionReported && exclude) { + ConsoleInstaller.message("excluding some .py files, like " + zipEntryName); + coreExclusionReported = true; + } + } + if (exclude) { + if (Installation.isVerbose() && zipEntry.isDirectory()) { + ConsoleInstaller.message("excluding directory " + zipEntryName); + } + } else { + count++; + if (count % threshold == 0) { + percent = percent + _progressListener.getInterval(); + _progressListener.progressChanged(percent); + } + createDirectories(targetDirectory, zipEntryName); + if (!zipEntry.isDirectory()) { + File file = createFile(targetDirectory, zipEntryName); + _progressListener.progressEntry(file.getAbsolutePath()); + FileOutputStream output = new FileOutputStream(file); + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = zipInput.read(buffer)) > 0) { + output.write(buffer, 0, len); + } + output.close(); + file.setLastModified(zipEntry.getTime()); + } + } + zipInput.closeEntry(); + zipEntry = zipInput.getNextEntry(); + } + if (!installationType.isStandalone()) { + // generate start scripts + _progressListener.progressStartScripts(); + StartScriptGenerator generator = new StartScriptGenerator(targetDirectory, javaHomeHandler); + generator.generateStartScripts(); + } else { + _progressListener.progressStandalone(); + File jythonJar = new File(targetDirectory, JYTHON_JAR); + File jythonPlainJar = new File(targetDirectory, "plain_" + JYTHON_JAR); + jythonJar.renameTo(jythonPlainJar); + File libDir = new File(targetDirectory, "Lib"); + StandalonePackager packager = new StandalonePackager(jythonJar); + packager.addJarFile(jythonPlainJar); + _progressListener.progressChanged(90); // approx + packager.addFullDirectory(libDir); + packager.close(); + // TODO:Oti move to FileHelper + StandalonePackager.emptyDirectory(targetDirectory, jythonJar); + } + // finish: inform listeners + _progressListener.progressFinished(); + Iterator installationListenersIterator = _installationListeners.iterator(); + while (installationListenersIterator.hasNext()) { + installationListenersIterator.next().progressFinished(); + } + } catch (IOException ioe) { + throw new InstallerException(Installation.getText(TextKeys.ERROR_ACCESS_JARFILE), ioe); + } + } + + public void addInstallationListener(InstallationListener installationListener) { + if (installationListener != null) { + _installationListeners.add(installationListener); + } + } + + private int approximateNumberOfEntries(InstallationType installationType) { + int numberOfEntries = 200; // core (minimum) + if (installationType.installLibraryModules()) { + if (installationType.isStandalone()) { + numberOfEntries += 450; + } else { + numberOfEntries += 1300; + } + } + if (installationType.installDemosAndExamples()) { + numberOfEntries += 70; + } + if (installationType.installDocumentation()) { + numberOfEntries += 500; + } + if (installationType.installSources()) { + numberOfEntries += 1000; + } + return numberOfEntries; + } + + private void createDirectories(final File targetDirectory, final String zipEntryName) { + int lastSepIndex = zipEntryName.lastIndexOf(PATH_SEPARATOR); + if (lastSepIndex > 0) { + File directory = new File(targetDirectory, zipEntryName.substring(0, lastSepIndex)); + if (directory.exists() && directory.isDirectory()) {} else { + if (!directory.mkdirs()) { + throw new InstallerException(Installation.getText(TextKeys.UNABLE_CREATE_DIRECTORY, + directory.getAbsolutePath())); + } + } + } + } + + private File createFile(final File targetDirectory, final String zipEntryName) + throws IOException { + File file = new File(targetDirectory, zipEntryName); + if (file.exists() && file.isFile()) {} else { + if (!file.createNewFile()) { + throw new InstallerException(Installation.getText(TextKeys.UNABLE_CREATE_FILE, + file.getCanonicalPath())); + } + } + return file; + } + + private List getCoreLibFiles() { + List coreLibFiles = new ArrayList(); + coreLibFiles.add("__future__.py"); + coreLibFiles.add("copy.py"); + coreLibFiles.add("dbexts.py"); + coreLibFiles.add("imaplib.py"); + coreLibFiles.add("isql.py"); + coreLibFiles.add("javaos.py"); + coreLibFiles.add("javapath.py"); + coreLibFiles.add("jreload.py"); + coreLibFiles.add("marshal.py"); + coreLibFiles.add("ntpath.py"); + coreLibFiles.add("os.py"); + coreLibFiles.add("popen2.py"); + coreLibFiles.add("posixpath.py"); + coreLibFiles.add("random.py"); + coreLibFiles.add("re.py"); + coreLibFiles.add("site.py"); + coreLibFiles.add("socket.py"); + coreLibFiles.add("sre.py"); + coreLibFiles.add("sre_compile.py"); + coreLibFiles.add("sre_constants.py"); + coreLibFiles.add("sre_parse.py"); + coreLibFiles.add("stat.py"); + coreLibFiles.add("string.py"); + coreLibFiles.add("threading.py"); + coreLibFiles.add("UserDict.py"); + coreLibFiles.add("zipfile.py"); + coreLibFiles.add("zlib.py"); + return coreLibFiles; + } + + private boolean shouldExcludeFile(InstallationType installationType, + List coreLibFiles, + ZipEntry zipEntry, + String zipEntryName) { + boolean exclude = false; + if (!installationType.installLibraryModules()) { + // handle files in Lib + if (!zipEntry.isDirectory() && zipEntryName.startsWith(LIB_NAME_SEP)) { + // include all files in /pawt subdirectory + if (!zipEntryName.startsWith(LIB_PAWT_SEP)) { + if (zipEntryName.endsWith(".py")) { // only compare *.py files + exclude = true; + Iterator coreLibFilesAsIterator = coreLibFiles.iterator(); + while (coreLibFilesAsIterator.hasNext()) { + String coreFileName = coreLibFilesAsIterator.next(); + if (zipEntryName.endsWith(PATH_SEPARATOR + coreFileName)) { + exclude = false; + } + } + } + } + } + } + return exclude; + } +} diff --git a/installer/src/java/org/python/util/install/JavaHomeHandler.java b/installer/src/java/org/python/util/install/JavaHomeHandler.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaHomeHandler.java @@ -0,0 +1,209 @@ +package org.python.util.install; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * Unified entry point for treatment of java.home + *

+ * Note that the system property java.home is never changed + */ +public final class JavaHomeHandler { + + public static final String JAVA_HOME = "java.home"; + + private static final String JAVA = "java"; + + private static final String JAVA_EXE = "java.exe"; + + private static final String BIN = "bin"; + + private static final String DEFAULT = "_default_"; + + private static final String EMPTY = ""; + + /** + * A map for java home strings and their respective executable names + */ + private static Map _executableNames; + + /** + * The current java home + */ + private String _currentJavaHome; + + /** + * create a java home handler for the default java home + */ + public JavaHomeHandler() { + this(DEFAULT); + } + + /** + * create a java home handler for a java home deviation + * + * @param currentJavaHome + * The deviated java home + */ + public JavaHomeHandler(String currentJavaHome) { + setCurrentJavaHome(currentJavaHome); + check(getCurrentJavaHome()); + } + + /** + * get the name of the java executable + * + * @return A name of a java executable which can be passed to {@link ChildProcess} + */ + public String getExecutableName() { + return getExecutableName(getCurrentJavaHome()); + } + + /** + * tell the validity of the current java home + *

+ * Note: if the current java home is not valid, {@link JavaHomeHandler#getExecutableName()} + * still returns the name of a callable java + * + * @return true if we have a valid java home, false otherwise. + */ + public boolean isValidHome() { + return !getFallbackExecutableName().equals(getExecutableName()); + } + + /** + * get the current java home, if it is valid + * + * @return The current java home + * @throws InstallerException + * if there is no valid java home + * + * @see JavaHomeHandler#isValidHome() + */ + public File getHome() throws InstallerException { + if (!isValidHome()) { + throw new InstallerException("no valid java home"); + } else { + return new File(getCurrentJavaHome()); + } + } + + /** + * @return true if the current java home is a deviation, false + * otherwise + */ + public boolean isDeviation() { + // make sure the default java home is also known + if (!getExecutableNames().containsKey(DEFAULT)) { + check(DEFAULT); + } + return !getExecutableName(DEFAULT).equals(getExecutableName(getCurrentJavaHome())); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(80); + builder.append("["); + if(!isValidHome()) { + builder.append("in"); + } + builder.append("valid java home: "); + builder.append(getCurrentJavaHome()); + builder.append("; executable: "); + builder.append(getExecutableName()); + builder.append("]"); + return builder.toString(); + } + + /** + * reset the handler (clear all stored java homes) + */ + static void reset() { + getExecutableNames().clear(); + } + + private String getExecutableName(String javaHome) { + Map executableNames = getExecutableNames(); + if (!executableNames.containsKey(javaHome)) { + check(javaHome); + } + return executableNames.get(javaHome); + } + + private void check(String javaHome) { + boolean valid = false; + boolean isDefault = false; + File javaExecutableFile = null; + if (DEFAULT.equals(javaHome)) { + isDefault = true; + javaHome = System.getProperty(JAVA_HOME, EMPTY); + } + if (javaHome.length() > 0) { + File javaHomeDir = new File(javaHome); + if (javaHomeDir.exists() && javaHomeDir.isDirectory()) { + File binDir = new File(javaHomeDir, BIN); + if (binDir.exists() && binDir.isDirectory()) { + javaExecutableFile = getExecutableFile(binDir); + if (javaExecutableFile.exists()) { + valid = true; + } + } + } + } + if (valid) { + addExecutable(javaHome, javaExecutableFile); + if (isDefault) { + addExecutable(DEFAULT, javaExecutableFile); + if (DEFAULT.equals(getCurrentJavaHome())) { + // update the current home to the real one + setCurrentJavaHome(javaHome); + } + } + } else { + addFallbackExecutable(javaHome); + if (isDefault) { + addFallbackExecutable(DEFAULT); + } + } + } + + private String getFallbackExecutableName() { + return JAVA; + } + + private void addFallbackExecutable(String javaHome) { + getExecutableNames().put(javaHome, getFallbackExecutableName()); + } + + private void addExecutable(String javaHome, File javaExecutableFile) { + getExecutableNames().put(javaHome, javaExecutableFile.getAbsolutePath()); + } + + private File getExecutableFile(File binDir) { + if (Installation.isWindows()) { + return new File(binDir, JAVA_EXE); + } else { + return new File(binDir, JAVA); + } + } + + private static Map getExecutableNames() { + if (_executableNames == null) { + _executableNames = new HashMap(); + } + return _executableNames; + } + + private String getCurrentJavaHome() { + if (_currentJavaHome == null) { + return DEFAULT; + } else { + return _currentJavaHome; + } + } + + private void setCurrentJavaHome(String currentJavaHome) { + _currentJavaHome = currentJavaHome.trim(); + } +} diff --git a/installer/src/java/org/python/util/install/JavaSelectionPage.java b/installer/src/java/org/python/util/install/JavaSelectionPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaSelectionPage.java @@ -0,0 +1,199 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; + +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +public class JavaSelectionPage extends AbstractWizardPage { + + private static final long serialVersionUID = 2871052924519223110L; + + private final static String _CURRENT_ACTION_COMMAND = "current"; + private final static String _OTHER_ACTION_COMMAND = "other"; + + private JRadioButton _currentButton; + private JRadioButton _otherButton; + + private JLabel _label; + private JTextField _javaHome; + private JButton _browse; + + public JavaSelectionPage() { + super(); + initComponents(); + } + + private void initComponents() { + // label for java home + _label = new JLabel(); + + // radio buttons + RadioButtonListener radioButtonListener = new RadioButtonListener(); + _currentButton = new JRadioButton(); + _currentButton.setActionCommand(_CURRENT_ACTION_COMMAND); + _currentButton.addActionListener(radioButtonListener); + _otherButton = new JRadioButton(); + _otherButton.setActionCommand(_OTHER_ACTION_COMMAND); + _otherButton.addActionListener(radioButtonListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_currentButton); + radioButtonGroup.add(_otherButton); + JPanel radioPanel = new JPanel(new GridLayout(0, 1)); + radioPanel.add(_currentButton); + radioPanel.add(_otherButton); + + // directory for java home + _javaHome = new JTextField(40); + _javaHome.addFocusListener(new JavaFocusListener()); + // browse button + _browse = new JButton(); + _browse.addActionListener(new BrowseButtonListener()); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(radioPanel, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panel.add(_javaHome, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panel.add(_browse, gridBagConstraints); + + add(panel); + } + + JTextField getJavaHome() { + return _javaHome; + } + + protected String getTitle() { + return getText(TARGET_JAVA_HOME_PROPERTY); + } + + protected String getDescription() { + return getText(CHOOSE_JRE); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _currentButton; + } + + protected void activate() { + _label.setText(getText(SELECT_JAVA_HOME) + ": "); + _currentButton.setText(getText(CURRENT)); + _otherButton.setText(getText(OTHER)); + _browse.setText(getText(BROWSE)); + setValues(); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private void setValues() { + boolean current = true; + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + if (javaHomeHandler.isDeviation()) { + current = false; + } + setCurrent(current); + } + + private void setCurrent(boolean current) { + if (current) { + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler()); + _currentButton.setSelected(true); + _otherButton.setSelected(false); + _javaHome.setEnabled(false); + _browse.setEnabled(false); + } else { + _currentButton.setSelected(false); + _otherButton.setSelected(true); + _javaHome.setEnabled(true); + _browse.setEnabled(true); + } + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + if (javaHomeHandler.isValidHome()) { + _javaHome.setText(javaHomeHandler.getHome().getAbsolutePath()); + } else { + _javaHome.setText(""); + } + _javaHome.setToolTipText(_javaHome.getText()); + } + + private final class BrowseButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(new File(_javaHome.getText())); + fileChooser.setDialogTitle(getText(SELECT_JAVA_HOME)); + // the filter is at the moment only used for the title of the dialog: + fileChooser.setFileFilter(new DirectoryFilter()); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (fileChooser.isAcceptAllFileFilterUsed()) { + if (Installation.isMacintosh() && Installation.isJDK141()) { + // work around ArrayIndexOutOfBoundsExceptio on Mac OS X, java version 1.4.1 + } else { + fileChooser.setAcceptAllFileFilterUsed(false); + } + } + int returnValue = fileChooser.showDialog(_browse, getText(SELECT)); + if (returnValue == JFileChooser.APPROVE_OPTION) { + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler(fileChooser.getSelectedFile().getAbsolutePath())); + setValues(); + } + } + } + + private final class RadioButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + setCurrent(_CURRENT_ACTION_COMMAND.equals(actionCommand)); + } + } + + private final class JavaFocusListener implements FocusListener { + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + String javaHome = _javaHome.getText(); + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler(javaHome)); + _javaHome.setToolTipText(javaHome); + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java b/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java @@ -0,0 +1,31 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +public class JavaSelectionPageValidator extends AbstractWizardValidator { + + JavaSelectionPage _page; + + JavaSelectionPageValidator(JavaSelectionPage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + String directory = _page.getJavaHome().getText().trim(); // trim to be sure + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(directory); + if(javaHomeHandler.isDeviation()) { + javaVersionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (javaVersionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + throw new ValidationException(javaVersionInfo.getReason()); + } + } else { + // no experiments if current java is selected + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); + } + FrameInstaller.setJavaHomeHandler(javaHomeHandler); + FrameInstaller.setJavaVersionInfo(javaVersionInfo); + } + +} diff --git a/installer/src/java/org/python/util/install/JavaVersionTester.java b/installer/src/java/org/python/util/install/JavaVersionTester.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaVersionTester.java @@ -0,0 +1,61 @@ +package org.python.util.install; + +import java.io.File; + +/** + * Helper class to test a java version + */ +public class JavaVersionTester { + + public static final String JAVA_HOME = "java.home"; + protected static final String JAVA_VERSION = "java.version"; + public static final String JAVA_SPECIFICATION_VERSION = "java.specification.version"; + protected static final String JAVA_VENDOR = "java.vendor"; + private static final String NEWLINE = "\n"; + + private static final String UNKNOWN = ""; + + public static void main(String[] args) { + if (args.length > 0) { + String tempFilePath = args[0]; + File tempFile = new File(tempFilePath); + if (tempFile.exists() && tempFile.canWrite()) { + try { + writeTempFile(tempFile); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } else { + if (!tempFile.exists()) { + System.err.println("temp file " + tempFilePath + " does not exist"); + } else { + System.err.println("cannot write to temp file " + tempFilePath); + } + System.exit(1); + } + } else { + System.err.println("no temp file given. usage: JavaVersionTester tempfile"); + System.out.println("exiting with 1"); + System.exit(1); + } + } + + private static void writeTempFile(File file) throws Exception { + FileHelper.write(file, createFileContent()); + } + + private static String createFileContent() { + StringBuffer sb = new StringBuffer(500); + String java_home = new JavaHomeHandler().getExecutableName(); + if (File.separatorChar != '/') { + java_home = java_home.replace(File.separatorChar, '/'); // backslash would be interpreted as escape char + } + sb.append(JAVA_HOME + "=" + java_home + NEWLINE); + sb.append(JAVA_VERSION + "=" + System.getProperty(JAVA_VERSION, UNKNOWN) + NEWLINE); + sb.append(JAVA_SPECIFICATION_VERSION + "=" + System.getProperty(JAVA_SPECIFICATION_VERSION, UNKNOWN) + NEWLINE); + sb.append(JAVA_VENDOR + "=" + System.getProperty(JAVA_VENDOR, UNKNOWN) + NEWLINE); + return sb.toString(); + } + +} diff --git a/installer/src/java/org/python/util/install/LanguagePage.java b/installer/src/java/org/python/util/install/LanguagePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LanguagePage.java @@ -0,0 +1,121 @@ +package org.python.util.install; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; + +public class LanguagePage extends AbstractWizardPage { + + private static Map _languageIndexes = new HashMap(2); + static { + _languageIndexes.put(Locale.ENGLISH, new Integer(0)); + _languageIndexes.put(Locale.GERMAN, new Integer(1)); + } + + private JLabel _label; + private JComboBox _languageBox; + private JarInfo _jarInfo; + private boolean _activated; + private boolean _stopListening; + + public LanguagePage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + _activated = false; + _stopListening = false; + initComponents(); + } + + private void initComponents() { + _label = new JLabel(); + add(_label); + _languageBox = new JComboBox(); + _languageBox.addActionListener(new LanguageBoxListener()); + add(_languageBox); + } + + protected String getTitle() { + return getText(WELCOME_TO_JYTHON); + } + + protected String getDescription() { + return getText(VERSION_INFO, _jarInfo.getVersion()); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _languageBox; + } + + protected void activate() { + _label.setText(getText(SELECT_LANGUAGE) + ": "); + // replace combo box items (localization) + int itemCount = _languageBox.getItemCount(); + _stopListening = true; // adding and removing fires an action event + for (int i = 0; i < itemCount; i++) { + _languageBox.removeItemAt(0); + } + _languageBox.addItem(getText(ENGLISH)); // keep indexes here + _languageBox.addItem(getText(GERMAN)); + _stopListening = false; + if (!_activated) { + // preselect German if default looks like German + if (Locale.getDefault().toString().startsWith(Locale.GERMAN.toString())) { + _languageBox.setSelectedIndex(getLanguageIndex(Locale.GERMAN)); + FrameInstaller.setLanguage(Locale.GERMAN); + } + } else { + _languageBox.setSelectedIndex(getLanguageIndex(FrameInstaller.getLanguage())); + } + _activated = true; + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private int getLanguageIndex(Locale locale) { + return ((Integer) _languageIndexes.get(locale)).intValue(); + } + + private Locale getLanguageFromIndex(int index) { + Integer indexInteger = new Integer(index); + Iterator languages = _languageIndexes.entrySet().iterator(); + while (languages.hasNext()) { + Map.Entry entry = (Map.Entry) languages.next(); + if (entry.getValue().equals(indexInteger)) { + return (Locale) entry.getKey(); + } + } + return null; + } + + private class LanguageBoxListener implements ActionListener { + public void actionPerformed(ActionEvent ae) { + if (!_stopListening) { + FrameInstaller.setLanguage(getLanguageFromIndex(_languageBox.getSelectedIndex())); + } + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/LicensePage.java b/installer/src/java/org/python/util/install/LicensePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LicensePage.java @@ -0,0 +1,120 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class LicensePage extends AbstractWizardPage { + + private static final String _ACCEPT_ACTION_COMMAND = "1"; + private static final String _DO_NOT_ACCEPT_ACTION_COMMAND = "0"; + + private JRadioButton _acceptButton; + private JRadioButton _doNotAcceptButton; + + public LicensePage(JarInfo jarInfo) { + super(); + initComponents(jarInfo); + } + + private void initComponents(JarInfo jarInfo) { + String licenseText = "n/a"; + try { + licenseText = jarInfo.getLicenseText(); + } catch (IOException e) { + e.printStackTrace(); + } + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new GridLayout(1, 1, 10, 10)); + JTextArea textArea = new JTextArea(13, 80); + JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + textArea.setEditable(false); + textArea.setText(licenseText); + centerPanel.add(scrollPane); + + // radio buttons + JPanel southPanel = new JPanel(); + RadioButtonListener radioButtonListener = new RadioButtonListener(); + _acceptButton = new JRadioButton(); + _acceptButton.setActionCommand(_ACCEPT_ACTION_COMMAND); + _acceptButton.addActionListener(radioButtonListener); + _doNotAcceptButton = new JRadioButton(); + _doNotAcceptButton.setActionCommand(_DO_NOT_ACCEPT_ACTION_COMMAND); + _doNotAcceptButton.addActionListener(radioButtonListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_acceptButton); + radioButtonGroup.add(_doNotAcceptButton); + JPanel radioPanel = new JPanel(new GridLayout(1, 0)); + radioPanel.add(_acceptButton); + radioPanel.add(_doNotAcceptButton); + southPanel.add(radioPanel); + + setLayout(new BorderLayout(0, 5)); + add(centerPanel, BorderLayout.CENTER); + add(southPanel, BorderLayout.SOUTH); + } + + protected String getTitle() { + return getText(LICENSE); + } + + protected String getDescription() { + return getText(PLEASE_READ_LICENSE); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _doNotAcceptButton; + } + + protected void activate() { + _acceptButton.setText(getText(ACCEPT)); + _doNotAcceptButton.setText(getText(DO_NOT_ACCEPT)); + boolean accept = FrameInstaller.isAccept(); + _acceptButton.setSelected(accept); + _doNotAcceptButton.setSelected(!accept); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + protected boolean isAccept() { + return _acceptButton.isSelected() && !_doNotAcceptButton.isSelected(); + } + + private final static class RadioButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + if (actionCommand.equals(_ACCEPT_ACTION_COMMAND)) { + FrameInstaller.setAccept(true); + } else { + FrameInstaller.setAccept(false); + } + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/LicensePageValidator.java b/installer/src/java/org/python/util/install/LicensePageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LicensePageValidator.java @@ -0,0 +1,17 @@ +package org.python.util.install; + +public class LicensePageValidator extends AbstractWizardValidator { + + LicensePage _page; + + LicensePageValidator(LicensePage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException { + if (!_page.isAccept()) { + throw new ValidationException(getText(PLEASE_ACCEPT_LICENSE)); + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/OverviewPage.java b/installer/src/java/org/python/util/install/OverviewPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/OverviewPage.java @@ -0,0 +1,243 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; + +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.python.util.install.Installation.JavaVersionInfo; + +public class OverviewPage extends AbstractWizardPage { + + private final static int _LONGER_LENGTH = 25; + private final static int _SHORTER_LENGTH = 10; + + private JLabel _directoryLabel; + private JLabel _typeLabel; + private JTextField _directory; + private JTextField _type; + private JLabel _message; + + private JLabel _osLabel; + private JCheckBox _osBox; + private JLabel _javaLabel; + private JTextField _javaVendor; + private JTextField _javaVersion; + private JCheckBox _javaBox; + + public OverviewPage() { + super(); + initComponents(); + } + + private void initComponents() { + _directoryLabel = new JLabel(); + _directory = new JTextField(_LONGER_LENGTH); + _directory.setEditable(false); + _directory.setFocusable(false); + _typeLabel = new JLabel(); + _type = new JTextField(_LONGER_LENGTH); + _type.setEditable(false); + _type.setFocusable(false); + + _osLabel = new JLabel(); + JTextField osName = new JTextField(_LONGER_LENGTH); + osName.setText(System.getProperty(Installation.OS_NAME)); + osName.setToolTipText(System.getProperty(Installation.OS_NAME)); + osName.setEditable(false); + osName.setFocusable(false); + JTextField osVersion = new JTextField(_SHORTER_LENGTH); + osVersion.setText(System.getProperty(Installation.OS_VERSION)); + osVersion.setToolTipText(System.getProperty(Installation.OS_VERSION)); + osVersion.setEditable(false); + osVersion.setFocusable(false); + _osBox = new JCheckBox(); + _osBox.setEnabled(false); + _osBox.setSelected(Installation.isValidOs()); + _osBox.setFocusable(false); + + _javaLabel = new JLabel(); + _javaLabel.setFocusable(false); + _javaVendor = new JTextField(_LONGER_LENGTH); + _javaVendor.setEditable(false); + _javaVendor.setFocusable(false); + _javaVersion = new JTextField(_SHORTER_LENGTH); + _javaVersion.setEditable(false); + _javaVersion.setFocusable(false); + _javaBox = new JCheckBox(); + _javaBox.setEnabled(false); + _javaBox.setFocusable(false); + + _message = new JLabel(); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_directoryLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panel.add(_directory, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(_typeLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(_type, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panel.add(_osLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panel.add(osName, gridBagConstraints); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 2; + panel.add(osVersion, gridBagConstraints); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 2; + panel.add(_osBox, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + panel.add(_javaLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + panel.add(_javaVendor, gridBagConstraints); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 3; + panel.add(_javaVersion, gridBagConstraints); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 3; + panel.add(_javaBox, gridBagConstraints); + + // attn special constraints for message (should always be on last row) + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.gridwidth = 2; + panel.add(_message, gridBagConstraints); + + add(panel); + } + + protected String getTitle() { + return getText(OVERVIEW_TITLE); + } + + protected String getDescription() { + return getText(OVERVIEW_DESCRIPTION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + // directory + _directoryLabel.setText(getText(TARGET_DIRECTORY_PROPERTY) + ": "); + _directory.setText(FrameInstaller.getTargetDirectory()); + _directory.setToolTipText(FrameInstaller.getTargetDirectory()); + + // type + _typeLabel.setText(getText(INSTALLATION_TYPE) + ": "); + InstallationType installationType = FrameInstaller.getInstallationType(); + String typeText; + if (installationType.isAll()) { + typeText = getText(ALL); + } else if (installationType.isStandard()) { + typeText = getText(STANDARD); + } else if (installationType.isMinimum()) { + typeText = getText(MINIMUM); + } else if (installationType.isStandalone()) { + typeText = getText(STANDALONE); + } else { + typeText = getText(CUSTOM); + typeText += " ("; + boolean predecessor = false; + if (installationType.installLibraryModules()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES; + predecessor = true; + } + if (installationType.installDemosAndExamples()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES; + predecessor = true; + } + if (installationType.installDocumentation()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_DOCUMENTATION; + predecessor = true; + } + if (installationType.installSources()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_SOURCES; + predecessor = true; + } + typeText += ")"; + } + _type.setText(typeText); + _type.setToolTipText(typeText); + + // os + _osLabel.setText(getText(OS_INFO) + ": "); + String osText; + if (_osBox.isSelected()) { + osText = getText(OK); + } else { + osText = getText(MAYBE_NOT_SUPPORTED); + } + _osBox.setText(osText); + + // java + _javaLabel.setText(getText(JAVA_INFO) + ": "); + JavaVersionInfo javaVersionInfo = FrameInstaller.getJavaVersionInfo(); + _javaVendor.setText(javaVersionInfo.getVendor()); + _javaVendor.setToolTipText(javaVersionInfo.getVendor()); + _javaVersion.setText(javaVersionInfo.getVersion()); + _javaVersion.setToolTipText(javaVersionInfo.getVersion()); + _javaBox.setSelected(Installation.isValidJava(javaVersionInfo)); + String javaText; + if (_javaBox.isSelected()) { + javaText = getText(OK); + } else { + javaText = getText(NOT_OK); + } + _javaBox.setText(javaText); + + // message + _message.setText(getText(CONFIRM_START, getText(NEXT))); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ProgressListener.java b/installer/src/java/org/python/util/install/ProgressListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ProgressListener.java @@ -0,0 +1,15 @@ +package org.python.util.install; + +public interface ProgressListener extends InstallationListener { + + public int getInterval(); + + public void progressChanged(int newPercentage); + + public void progressEntry(String entry); + + public void progressStartScripts(); + + public void progressStandalone(); + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ProgressPage.java b/installer/src/java/org/python/util/install/ProgressPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ProgressPage.java @@ -0,0 +1,122 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.io.File; +import java.io.IOException; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +import org.python.util.install.driver.Autotest; + +public class ProgressPage extends AbstractWizardPage implements ProgressListener { + + private static final long serialVersionUID = 9013748834030994976L; + + private JarInfo _jarInfo; + private JLabel _label; + private JProgressBar _progressBar; + private JLabel _progressEntry; + private Autotest _autotest; + + public ProgressPage(JarInfo jarInfo, Autotest autotest) { + super(); + _jarInfo = jarInfo; + _autotest = autotest; + initComponents(); + } + + private void initComponents() { + JPanel northPanel = new JPanel(); + _label = new JLabel(); + northPanel.add(_label); + _progressBar = new JProgressBar(); + northPanel.add(_progressBar); + JPanel centerPanel = new JPanel(); + _progressEntry = new JLabel(); + centerPanel.add(_progressEntry); + setLayout(new BorderLayout(0, 5)); + add(northPanel, BorderLayout.NORTH); + add(centerPanel, BorderLayout.CENTER); + } + + protected String getTitle() { + return getText(INSTALLATION_IN_PROGRESS); + } + + protected String getDescription() { + return getText(PLEASE_WAIT); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return false; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + _label.setText(getText(PROGRESS) + ": "); + _progressBar.setValue(0); + _progressBar.setStringPainted(true); + try { + _progressEntry.setText(getText(INFLATING, _jarInfo.getJarFile().getName())); + } catch (IOException e) { + // should not happen + } + JarInstaller jarInstaller = new JarInstaller(this, _jarInfo); + if (_autotest != null) { + jarInstaller.addInstallationListener(_autotest); + } + File targetDirectory = new File(FrameInstaller.getTargetDirectory()); + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + jarInstaller.inflate(targetDirectory, FrameInstaller.getInstallationType(), javaHomeHandler); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + // + // interface ProgressListener + // + + public void progressChanged(int newPercentage) { + _progressBar.setValue(newPercentage); + } + + public int getInterval() { + return 1; + } + + public void progressFinished() { + _progressBar.setValue(100); + getWizard().gotoNextPage(); + } + + public void progressEntry(String entry) { + _progressEntry.setText(getText(INFLATING, entry)); + } + + public void progressStartScripts() { + _progressEntry.setText(getText(GENERATING_START_SCRIPTS)); + } + + public void progressStandalone() { + _progressEntry.setText(getText(PACKING_STANDALONE_JAR)); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ReadmePage.java b/installer/src/java/org/python/util/install/ReadmePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ReadmePage.java @@ -0,0 +1,75 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.io.IOException; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class ReadmePage extends AbstractWizardPage { + + private JTextArea _textArea; + private JarInfo _jarInfo; + + public ReadmePage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new GridLayout(1, 1)); + _textArea = new JTextArea(13, 80); + JScrollPane scrollPane = new JScrollPane(_textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + _textArea.setEditable(false); + _textArea.setText("n/a"); + centerPanel.add(scrollPane); + + setLayout(new BorderLayout(0, 5)); + add(centerPanel, BorderLayout.CENTER); + } + + protected String getTitle() { + return getText(README); + } + + protected String getDescription() { + return getText(PLEASE_README); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + try { + _textArea.setText(_jarInfo.getReadmeText()); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/StandalonePackager.java b/installer/src/java/org/python/util/install/StandalonePackager.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/StandalonePackager.java @@ -0,0 +1,184 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +public class StandalonePackager { + + private final static int BUF_SIZE = 1024; + + private File _jarFile; + private Manifest _manifest; + private JarOutputStream _jarOut; + + /** + * Helper class to pack stuff into a single .jar file. + *

+ * If a .jar file has to be added, this should be the first add (because of the MANIFEST). + */ + public StandalonePackager(File jarFile) { + _jarFile = jarFile; + } + + /** + * add a file, in given parent dir (null = top) + * + * @param file to write to jar + * @param parentDir as String + * + * @throws IOException + */ + public void addFile(File file, String parentDir) throws IOException { + byte[] buffer = new byte[BUF_SIZE]; + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + String jarEntryName = null; + if (parentDir != null && parentDir.length() > 0) { + jarEntryName = parentDir + "/" + file.getName(); + } else { + jarEntryName = file.getName(); + } + getJarOutputStream().putNextEntry(new JarEntry(jarEntryName)); + for (int read = 0; read != -1; read = inputStream.read(buffer)) { + getJarOutputStream().write(buffer, 0, read); + } + getJarOutputStream().closeEntry(); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + /** + * add a full directory + * + * @param directory + * @throws IOException + */ + public void addFullDirectory(File directory) throws IOException { + addDirectory(directory, null); + } + + /** + * add the contents of a given jar file + * + * @param jarFile to add + * @throws IOException + * @throws FileNotFoundException + */ + public void addJarFile(File jarFile) throws FileNotFoundException, IOException { + JarFile jarJarFile = new JarFile(jarFile); + try { + _manifest = jarJarFile.getManifest(); + } finally { + jarJarFile.close(); + } + + JarInputStream inputStr = null; + try { + inputStr = new JarInputStream(new FileInputStream(jarFile)); + JarEntry entry = inputStr.getNextJarEntry(); + while (entry != null) { + getJarOutputStream().putNextEntry(entry); + byte[] buffer = new byte[BUF_SIZE]; + int len; + while ((len = inputStr.read(buffer)) > 0) { + getJarOutputStream().write(buffer, 0, len); + } + getJarOutputStream().closeEntry(); + entry = inputStr.getNextJarEntry(); + } + } finally { + if (inputStr != null) { + try { + inputStr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public void close() throws FileNotFoundException, IOException { + getJarOutputStream().close(); + } + + /** + * removes all files in directory dir (and subdirectories), except excludeFile. + * + * @param dir + * @param excludeFile + */ + public static void emptyDirectory(File dir, File excludeFile) { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + if (!files[i].equals(excludeFile)) { + if (files[i].isDirectory()) { + emptyDirectory(files[i], excludeFile); + } else { + files[i].delete(); + } + } + } + if (dir.listFiles().length == 0) { + dir.delete(); + } + } + + /** + * add the given directory to the jar, including all subdirectories, in given parent dir (null = + * top) + * + * @param dir to add to the jar + * @param parentDir to save in jar + * @throws IOException + */ + private void addDirectory(File dir, String parentDir) throws IOException { + if (!dir.isDirectory()) + return; + File[] filesInDir = dir.listFiles(); + for (int i = 0; i < filesInDir.length; i++) { + File currentFile = filesInDir[i]; + if (currentFile.isFile()) { + if (parentDir != null && parentDir.length() > 0) { + addFile(currentFile, parentDir + "/" + dir.getName()); + } else { + addFile(currentFile, dir.getName()); + } + } else { + String newParentDir = null; + if (parentDir != null && parentDir.length() > 0) { + newParentDir = parentDir + "/" + dir.getName(); + } else { + newParentDir = dir.getName(); + } + addDirectory(currentFile, newParentDir); + } + } + } + + /** + * @return the jar output stream + */ + private JarOutputStream getJarOutputStream() throws FileNotFoundException, IOException { + if (_jarOut == null) { + if (_manifest != null) { + _jarOut = new JarOutputStream(new FileOutputStream(_jarFile), _manifest); + } else { + _jarOut = new JarOutputStream(new FileOutputStream(_jarFile)); + } + } + return _jarOut; + } + +} diff --git a/installer/src/java/org/python/util/install/StartScriptGenerator.java b/installer/src/java/org/python/util/install/StartScriptGenerator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/StartScriptGenerator.java @@ -0,0 +1,240 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Date; + +public class StartScriptGenerator { + + protected final static int UNIX_FLAVOUR = 10; + + protected final static int WINDOWS_FLAVOUR = 30; + + protected final static int BOTH_FLAVOUR = 50; + + protected final static String WIN_CR_LF; + + private final static String JAVA_HOME = "JAVA_HOME"; + + /** do not hard-wire JYTHON_HOME */ + private final static String JYTHON_HOME_FALLBACK = "JYTHON_HOME_FALLBACK"; + + private final static String JYTHON = "jython"; + + private final static String JYTHON_BAT = "jython.bat"; + static { + int dInt = Integer.parseInt("0d", 16); + int aInt = Integer.parseInt("0a", 16); + WIN_CR_LF = new String(new char[] {(char)dInt, (char)aInt}); + } + + private File _targetDirectory; + + private JavaHomeHandler _javaHomeHandler; + + private int _flavour; + + public StartScriptGenerator(File targetDirectory, JavaHomeHandler javaHomeHandler) { + _targetDirectory = targetDirectory; + _javaHomeHandler = javaHomeHandler; + if (Installation.isWindows()) { + setFlavour(WINDOWS_FLAVOUR); + } else { + // everything else defaults to unix at the moment + setFlavour(UNIX_FLAVOUR); + } + } + + protected void setFlavour(int flavour) { + _flavour = flavour; + if (flavour == WINDOWS_FLAVOUR) { + // check if we should create unix like scripts, too + if (hasUnixlikeShell()) { + _flavour = BOTH_FLAVOUR; + } + } + } + + protected int getFlavour() { + return _flavour; + } + + protected boolean hasUnixlikeShell() { + int errorCode = 0; + try { + String command[] = new String[] {"sh", "-c", "env"}; + long timeout = 3000; + ChildProcess childProcess = new ChildProcess(command, timeout); + childProcess.setDebug(false); + childProcess.setSilent(true); + errorCode = childProcess.run(); + } catch (Throwable t) { + errorCode = 1; + } + return errorCode == 0; + } + + protected final void generateStartScripts() throws IOException { + File bin = new File(getTargetDirectory(), "bin"); + File bin_jython = new File(bin, JYTHON); + switch(getFlavour()){ + case BOTH_FLAVOUR: + writeToTargetDir(JYTHON_BAT, getJythonScript(WINDOWS_FLAVOUR)); + FileHelper.makeExecutable(writeToTargetDir(JYTHON, getJythonScript(BOTH_FLAVOUR))); + FileHelper.makeExecutable(bin_jython); + break; + case WINDOWS_FLAVOUR: + writeToTargetDir(JYTHON_BAT, getJythonScript(WINDOWS_FLAVOUR)); + // delete the *nix script in /bin dir + bin_jython.delete(); + break; + default: + FileHelper.makeExecutable(writeToTargetDir(JYTHON, getJythonScript(UNIX_FLAVOUR))); + FileHelper.makeExecutable(bin_jython); + // delete the windows script in /bin dir + File bin_jython_bat = new File(bin, JYTHON_BAT); + bin_jython_bat.delete(); + break; + } + } + + /** + * only protected for unit test use + */ + protected final String getJythonScript(int flavour) throws IOException { + if (flavour == WINDOWS_FLAVOUR) { + return getStartScript(getWindowsJythonTemplate()) + readFromFile(JYTHON_BAT); + } else { + return getStartScript(getUnixJythonTemplate()) + readFromFile(JYTHON); + } + } + + /** + * These placeholders are valid for all private methods: + * + * {0} : current date
+ * {1} : user.name
+ * {2} : target directory
+ */ + private String getStartScript(String template) throws IOException { + String parameters[] = new String[4]; + parameters[0] = new Date().toString(); + parameters[1] = System.getProperty("user.name"); + parameters[2] = getTargetDirectory().getCanonicalPath(); + return MessageFormat.format(template, (Object[])parameters); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private String getWindowsJythonTemplate() { + StringBuilder builder = getWindowsHeaderTemplate(); + builder.append("set "); + builder.append(JAVA_HOME); + builder.append("="); + if (_javaHomeHandler.isValidHome()) { + builder.append("\""); + builder.append(_javaHomeHandler.getHome().getAbsolutePath()); + builder.append("\""); + } + builder.append(WIN_CR_LF); + builder.append("set "); + builder.append(JYTHON_HOME_FALLBACK); + builder.append("=\"{2}\""); + builder.append(WIN_CR_LF); + builder.append(WIN_CR_LF); + return builder.toString(); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private StringBuilder getWindowsHeaderTemplate() { + StringBuilder builder = new StringBuilder(1000); + builder.append("@echo off"); + builder.append(WIN_CR_LF); + builder.append("rem This file was generated by the Jython installer"); + builder.append(WIN_CR_LF); + builder.append("rem Created on {0} by {1}"); + builder.append(WIN_CR_LF); + builder.append(WIN_CR_LF); + return builder; + } + + /** + * placeholders: + * + * @see getStartScript + */ + private String getUnixJythonTemplate() { + StringBuilder builder = getUnixHeaderTemplate(); + builder.append(JAVA_HOME); + builder.append("="); + if (_javaHomeHandler.isValidHome()) { + builder.append("\""); + builder.append(_javaHomeHandler.getHome().getAbsolutePath()); + builder.append("\""); + } + builder.append("\n"); + builder.append(JYTHON_HOME_FALLBACK); + builder.append("=\"{2}\"\n"); + builder.append("\n"); + return builder.toString(); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private StringBuilder getUnixHeaderTemplate() { + StringBuilder builder = new StringBuilder(1000); + builder.append("#!/usr/bin/env bash\n"); + builder.append("\n"); + builder.append("# This file was generated by the Jython installer\n"); + builder.append("# Created on {0} by {1}\n"); + builder.append("\n"); + return builder; + } + + /** + * @param fileName + * The short file name, e.g. JYTHON_BAT + * + * @throws IOException + */ + private String readFromFile(String fileName) throws IOException { + // default runtime location + File targetDirectory = getTargetDirectory(); + File file = new File(new File(targetDirectory, "bin"), fileName); + if (!file.exists()) { + // deviation: test time location + file = new File(targetDirectory, fileName); + } + return FileHelper.readAll(file); + } + + /** + * Create (or overwrite) the specified file in the target directory + * + * @param fileName + * The short file name, e.g. JYTHON_BAT + * @param contents + * + * @throws IOException + */ + private File writeToTargetDir(String fileName, String contents) throws IOException { + File file = new File(getTargetDirectory(), fileName); + FileHelper.write(file, contents); + return file; + } + + private File getTargetDirectory() { + return _targetDirectory; + } +} diff --git a/installer/src/java/org/python/util/install/SuccessPage.java b/installer/src/java/org/python/util/install/SuccessPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/SuccessPage.java @@ -0,0 +1,55 @@ +package org.python.util.install; + +import javax.swing.JComponent; +import javax.swing.JLabel; + +public class SuccessPage extends AbstractWizardPage { + + private JarInfo _jarInfo; + private JLabel _label; + + public SuccessPage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + _label = new JLabel(); + add(_label); + } + + protected String getTitle() { + return getText(CONGRATULATIONS); + } + + protected String getDescription() { + return getText(SUCCESS, _jarInfo.getVersion(), FrameInstaller.getTargetDirectory()); + } + + protected boolean isCancelVisible() { + return false; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + _label.setText(getText(PRESS_FINISH, getWizard().getFinishString())); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants.java b/installer/src/java/org/python/util/install/TextConstants.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants.java @@ -0,0 +1,148 @@ +package org.python.util.install; + +import java.util.ListResourceBundle; + +public class TextConstants extends ListResourceBundle implements TextKeys { + + static final Object[][] contents = { + // LOCALIZE THIS + + { ACCEPT, "I accept" }, // license + { ALL, "All (everything, including sources)" }, // installation type + { BROWSE, "Browse..." }, // button (open the JFileChooser) + { CANCEL, "Cancel" }, // button + { CHOOSE_LOCATION, "Choose the location where you want Jython to be installed to" }, // selection + { CHOOSE_JRE, "Choose the java version (JRE/JDK) to run Jython with" }, // selection + { CONFIRM_START, "Please press {0} to start the installation" }, // overview + { CONGRATULATIONS, "Congratulations!" }, // congratulations + { CORE, "Core" }, // installation type + { CREATED_DIRECTORY, "Created directory {0}" }, // directory + { CURRENT, "Current" }, // directory + { CUSTOM, "Custom" }, // installation type + { DEMOS_EXAMPLES, "Demos and examples" }, // installation type + { DO_NOT_ACCEPT, "I do not accept" }, // license + { DIRECTORIES_ONLY, "Directories only" }, // file chooser + { DOCUMENTATION, "Documentation" }, // installation type + { EMPTY_TARGET_DIRECTORY, "Target directory must not be empty" }, // error + { ENGLISH, "English" }, // language + { ERROR, "Error" }, // error + { ERROR_ACCESS_JARFILE, "Error accessing jar file" }, // error + { FINISH, "Finish" }, // button + { GENERATING_START_SCRIPTS, "Generating start scripts ..." }, // progress + { GERMAN, "German" }, // language + { INFLATING, "Inflating {0}" }, // progress + { INFORMATION, "Information" }, // information + { INSTALLATION_CANCELLED, "Installation cancelled." }, // final + { INSTALLATION_IN_PROGRESS, "The installation is now in progress" }, // progress + { INSTALLATION_TYPE_DESCRIPTION, "The following installation types are available" }, // installation type + { INSTALLATION_TYPE, "Installation type" }, // installation type + { JAR_NOT_FOUND, "Unable to find jar file {0}." }, // error + { JAVA_INFO, "Java vendor / version" }, // version + { JYTHON_INSTALL, "Jython Installation" }, // title + { LANGUAGE_PROPERTY, "Language" }, // language + { LIBRARY_MODULES, "Library modules" }, // installation type + { LICENSE, "License agreement" }, // license + { MAYBE_NOT_SUPPORTED, "Maybe not supported" }, // version + { MINIMUM, "Minimum (core)" }, // installation type + { NEXT, "Next" }, // button + { NON_EMPTY_TARGET_DIRECTORY, "Target directory is not empty" }, // error + { NO_MANIFEST, "No manifest found in jar file {0}." }, // error + { NOT_OK, "Not ok !" }, // version + { OK, "Ok" }, // version + { OS_INFO, "OS name / version" }, // version + { OTHER, "Other" }, // directory + { OVERVIEW_DESCRIPTION, "The installation will be done using the following options" }, // overview + { OVERVIEW_TITLE, "Overview (summary of options)" }, // overview + { PACKING_STANDALONE_JAR, "Packing standalone " + JarInstaller.JYTHON_JAR + " ..." }, // progress + { PLEASE_ACCEPT_LICENSE, "Please read and accept the license agreement" }, // license + { PLEASE_README, "Please read the following information" }, // readme + { PLEASE_READ_LICENSE, "Please read the license agreement carefully" }, // license + { PLEASE_WAIT, "Please stand by, this may take a few seconds ..." }, // progress + { PRESS_FINISH, "Please press {0} to exit the installation." }, // finish + { PREVIOUS, "Previous" }, // button + { PROGRESS, "Progress" }, // progress + { README, "README" }, // readme + { SELECT, "Select" }, // button (approval in JFileChooser) + { SELECT_INSTALLATION_TYPE, "Please select the installation type" }, // installation type + { SELECT_JAVA_HOME, "Please select the java home directory" }, // directory + { SELECT_LANGUAGE, "Please select your language" }, // language + { SELECT_TARGET_DIRECTORY, "Please select the target directory" }, // directory + { SOURCES, "Sources" }, // installation type + { STANDARD, "Standard (core, library modules, demos, examples, documentation)" }, // installation type + { STANDALONE, "Standalone (a callable .jar file)" }, // installation type + { SUCCESS, "You successfully installed Jython {0} to directory {1}." }, // success + { TARGET_DIRECTORY_PROPERTY, "Target directory" }, // property as title + { TARGET_JAVA_HOME_PROPERTY, "Target java home" }, // property as title + { UNABLE_CREATE_DIRECTORY, "Unable to create directory {0}." }, // error + { UNABLE_CREATE_FILE, "Unable to create file {0}." }, // error + { UNABLE_TO_DELETE, "Unable to delete {0}" }, // error + { UNEXPECTED_URL, "Unexpected URL {0} found for installation jar file." }, // error + { VERSION_INFO, "You are about to install Jython version {0}" }, // version + { WELCOME_TO_JYTHON, "Welcome to Jython !" }, // welcome + { ZIP_ENTRY_SIZE, "Size of zip entry {0} unknown." }, // error + { ZIP_ENTRY_TOO_BIG, "Zip entry {0} too big." }, // error + + // console texts (C_*) should not contain special characters (like e.g. ü) + { C_ACCEPT, "Do you accept the license agreement ?" }, // license + { C_ALL, "All (everything, including sources)" }, // installation type + { C_AT_ANY_TIME_CANCEL, "(at any time, answer {0} to cancel the installation)" }, // console + { C_AVAILABLE_LANGUAGES, "For the installation process, the following languages are available: {0}" }, // console + { C_CHECK_JAVA_VERSION, "Checking java version ..." }, // progress + { C_CLEAR_DIRECTORY, "Contents of directory {0} will be deleted now! Are you sure to proceed ?" }, //console + { C_CONFIRM_TARGET, "Please confirm copying of files to directory {0}" }, // console + { C_CONGRATULATIONS, "Congratulations!" }, // congratulations + { C_CREATE_DIRECTORY, "Unable to find directory {0}, create it ?" }, // console + { C_ENTER_TARGET_DIRECTORY, "Please enter the target directory" }, // console + { C_ENTER_JAVA_HOME, "Please enter the java home directory (empty for using the current java runtime)" }, // console + { C_ENGLISH, "English" }, // language + { C_EXCLUDE, "Do you want to exclude parts from the installation ?" }, // installation type + { C_GENERATING_START_SCRIPTS, "Generating start scripts ..." }, // progress + { C_GERMAN, "German" }, // language + { C_INCLUDE, "Do you want to install additional parts ?" }, // installation type + { C_INEXCLUDE_PARTS, "The following parts are selectable ({0} = no more)" }, // installation type + { C_INSTALL_TYPES, "The following installation types are available:" }, // installation type + { C_INVALID_ANSWER, "Answer {0} is not valid here" }, // error + { C_JAVA_VERSION, "Your java version to start Jython is: {0} / {1}" }, // version + { C_MINIMUM, "Minimum (core)" }, // installation type + { C_NO, "n" }, // answer + { C_NO_BIN_DIRECTORY, "There is no /bin directory below {0}." }, // error + { C_NO_JAVA_EXECUTABLE, "No java executable found in {0}." }, // error + { C_NO_VALID_JAVA, "No valid java found in {0}." }, // error + { C_NON_EMPTY_TARGET_DIRECTORY, "Target directory {0} is not empty" }, // error + { C_NOT_A_DIRECTORY, "{0} is not a directory. " }, // error + { C_NOT_FOUND, "{0} not found. " }, // error + { C_OS_VERSION, "Your operating system version is: {0} / {1}" }, // version + { C_OVERWRITE_DIRECTORY, "Directory {0} is not empty - ok to overwrite contents ?" }, // console + { C_PACKING_STANDALONE_JAR, "Packing standalone " + JarInstaller.JYTHON_JAR + " ..." }, // progress + { C_PROCEED, "Please press Enter to proceed" }, // console + { C_PROCEED_ANYWAY, "Please press Enter to proceed anyway" }, // console + { C_READ_LICENSE, "Do you want to read the license agreement now ?" }, // license + { C_READ_README, "Do you want to show the contents of README ?" }, // readme + { C_SCHEDULED, "{0} scheduled for installation" }, // installation type + { C_SELECT_INSTALL_TYPE, "Please select the installation type" }, // installation type + { C_SELECT_LANGUAGE, "Please select your language" }, // language + { C_SILENT_INSTALLATION, "Performing silent installation" }, // installation mode + { C_STANDALONE, "Standalone (a single, executable .jar)" }, //installation mode + { C_STANDARD, "Standard (core, library modules, demos and examples, documentation)" }, // installation type + { C_SUCCESS, "You successfully installed Jython {0} to directory {1}." }, // success + { C_SUMMARY, "Summary:" }, // summary + { C_TO_CURRENT_JAVA, "Warning: switching back to current JDK due to error: {0}." }, // warning + { C_UNABLE_CREATE_DIRECTORY, "Unable to create directory {0}." }, // error + { C_UNABLE_CREATE_TMPFILE, "Unable to create temp file {0}." }, // error + { C_UNSCHEDULED, "{0} excluded from installation" }, // installation type + { C_UNABLE_TO_DELETE, "Unable to delete {0}" }, // error + { C_UNSUPPORTED_JAVA, "This java version is not supported." }, // version + { C_UNSUPPORTED_OS, "This operating system might not be fully supported." }, // version + { C_USING_TYPE, "Using installation type {0}" }, // installation type + { C_VERSION_INFO, "You are about to install Jython version {0}" }, // version + { C_WELCOME_TO_JYTHON, "Welcome to Jython !" }, // welcome + { C_YES, "y" }, // answer + + // END OF MATERIAL TO LOCALIZE + }; + + public Object[][] getContents() { + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants_de.java b/installer/src/java/org/python/util/install/TextConstants_de.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants_de.java @@ -0,0 +1,148 @@ +package org.python.util.install; + +import java.util.ListResourceBundle; + +public class TextConstants_de extends ListResourceBundle implements TextKeys, UnicodeSequences { + + static final Object[][] contents = { + // Die folgenden Texte duerfen Umlaute und Sonderzeichen enthalten, aber nur als Unicode Escape Sequenzen aus UnicodeSequences + // The following texts may contain special characters, but only as unicode escape sequences from UnicodeSequences + { ACCEPT, "Ja, ich akzeptiere" }, // license + { ALL, "Alles (volle Installation, inklusive Quellcode)" }, // installation type + { BROWSE, "Suchen..." }, // button (open the JFileChooser) + { CANCEL, "Abbrechen" }, // button text + { CHOOSE_LOCATION, "W"+a2+"hlen Sie das Verzeichnis, in das Jython installiert werden soll" }, // selection + { CHOOSE_JRE, "Bestimmen Sie die Java Version (JRE/JDK), mit welcher Jython gestartet werden soll" }, // selection + { CONFIRM_START, "Bitte dr"+u2+"cken Sie {0}, um die Installation zu starten" }, // overview + { CONGRATULATIONS, "Gratulation!" }, // congratulations + { CORE, "Kern" }, // installation type + { CREATED_DIRECTORY, "Verzeichnis {0} wurde erstellt" }, // directory + { CURRENT, "Das aktuelle" }, // directory + { CUSTOM, "Benutzerdefiniert" }, // installation type + { DEMOS_EXAMPLES, "Demos und Beispiele" }, // installation type + { DIRECTORIES_ONLY, "Nur Verzeichnisse" }, // file chooser + { DOCUMENTATION, "Dokumentation" }, // installation type + { DO_NOT_ACCEPT, "Nein, ich akzeptiere nicht" }, // license + { EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis darf nicht leer sein" }, // error + { ENGLISH, "Englisch" }, // language + { ERROR, "Fehler" }, // error + { ERROR_ACCESS_JARFILE, "Problem beim Zugriff auf das jar File" }, // error + { FINISH, "Beenden" }, // button + { GENERATING_START_SCRIPTS, "Start Scripts werden generiert ..." }, // progress + { GERMAN, "Deutsch" }, // language + { INFLATING, "Entpacke {0}" }, // progress + { INFORMATION, "Information" }, // information + { INSTALLATION_CANCELLED, "Sie haben die Installation abgebrochen." }, // final + { INSTALLATION_IN_PROGRESS, "Die Installation l"+a2+"uft" }, // progress + { INSTALLATION_TYPE_DESCRIPTION, "Die folgenden Installationstypen sind verf"+u2+"gbar" }, // installation type + { INSTALLATION_TYPE, "Installationstyp" }, // installation type + { JAVA_INFO, "Java Hersteller / Version" }, // version + { JAR_NOT_FOUND, "Jar File {0} nicht gefunden." }, // error + { JYTHON_INSTALL, "Jython Installation" }, // title + { LANGUAGE_PROPERTY, "Sprache" }, // language + { LIBRARY_MODULES, "Bibliotheksmodule" }, // installation type + { LICENSE, "Lizenzvereinbarung" }, // license + { MAYBE_NOT_SUPPORTED, "Eventuell nicht unterst"+u2+"tzt" }, // version + { MINIMUM, "Minimum (Kern)" }, // installation type + { NEXT, "Weiter" }, // button + { NON_EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis enth"+a2+"lt bereits Daten." }, // error + { NO_MANIFEST, "Jar File {0} enth"+a2+"lt kein Manifest." }, // error + { NOT_OK, "Nicht ok !" }, // version + { OK, "Ok" }, // version + { OS_INFO, "Betriebssystem / Version" }, // version + { OTHER, "Ein abweichendes" }, // directory + { OVERVIEW_DESCRIPTION, "Sie haben folgende Einstellungen f"+u2+"r die Installation ausgew"+a2+"hlt" }, // overview + { OVERVIEW_TITLE, U2+"bersicht "+u2+"ber die gew"+a2+"hlten Einstellungen" }, // overview + { PACKING_STANDALONE_JAR, "Das standalone " + JarInstaller.JYTHON_JAR + " File wird erstellt ..." }, // progress + { PLEASE_ACCEPT_LICENSE, "Bitte lesen und akzeptieren Sie die Lizenzvereinbarung" }, // license + { PLEASE_README, "Bitte lesen Sie die folgenden Informationen" }, // readme + { PLEASE_READ_LICENSE, "Bitte lesen Sie die Lizenzvereinbarung sorf"+a2+"ltig durch" }, // license + { PLEASE_WAIT, "Bitte um etwas Geduld, die Installation kann einige Sekunden dauern ..." }, // progress + { PRESS_FINISH, "Bitte dr"+u2+"cken Sie {0}, um die Installation abzuschliessen." }, // finish + { PREVIOUS, "Zur"+u2+"ck" }, // button + { PROGRESS, "Fortschritt" }, // progress + { README, "README" }, // readme + { SELECT, "Ausw"+a2+"hlen" }, // button (approval in JFileChooser) + { SELECT_INSTALLATION_TYPE, "Bitte w"+a2+"hlen Sie den Installationstyp" }, // installation type + { SELECT_JAVA_HOME, "Bitte w"+a2+"hlen Sie das Java Home Verzeichnis" }, // directory + { SELECT_LANGUAGE, "Bitte w"+a2+"hlen Sie Ihre Sprache" }, // language + { SELECT_TARGET_DIRECTORY, "Bitte w"+a2+"hlen Sie das Zielverzeichnis" }, // directory + { SOURCES, "Quellcode" }, // installation type + { STANDARD, "Standard (Kern, Bibliotheksmodule, Demos, Beispiele, Dokumentation)" }, // installation type + { STANDALONE, "Standalone (ein ausf"+u2+"hrbares .jar File)" }, // installation type + { SUCCESS, "Sie haben Jython {0} erfolgreich im Verzeichnis {1} installiert." }, // final + { TARGET_DIRECTORY_PROPERTY, "Zielverzeichnis" }, // property als Titel + { TARGET_JAVA_HOME_PROPERTY, "Java Home Verzeichnis" }, // property als Titel + { UNABLE_CREATE_DIRECTORY, "Fehler beim Erstellen von Verzeichnis {0}." }, // error + { UNABLE_CREATE_FILE, "Fehler beim Erstellen von File {0}." }, // error + { UNABLE_TO_DELETE, "Fehler beim L"+o2+"schen von {0}" }, // console + { UNEXPECTED_URL, "Das Jar File f"+u2+"r die Installation weist eine unerwartete URL {0} auf." }, // error + { VERSION_INFO, "Sie sind im Begriff, Jython Version {0} zu installieren." }, // version + { WELCOME_TO_JYTHON, "Willkommen bei Jython !" }, // welcome + { ZIP_ENTRY_SIZE, "Der Zip Eintrag {0} hat eine unbekannte Gr"+o2+"sse." }, // error + { ZIP_ENTRY_TOO_BIG, "Der Zip Eintrag {0} ist zu gross." }, // error + + // Konsole Texte (beginnend mit C_) duerfen keine Umlaute und andere Sonderzeichen enthalten: + // console texts (beginning with C_) must not contain special characters (use ASCII only): + { C_ACCEPT, "Akzeptieren Sie die Lizenzvereinbarung ?" }, // license + { C_ALL, "Alles (volle Installation, inklusive Quellcode)" }, // installation type + { C_AT_ANY_TIME_CANCEL, "(Sie koennen die Installation jederzeit durch Eingabe von {0} abbrechen)" }, // console + { C_AVAILABLE_LANGUAGES, "Die folgenden Sprachen sind fuer den Installationsvorgang verfuegbar: {0}" }, // languages + { C_CHECK_JAVA_VERSION, "Ueberpruefung der Java Version ..." }, // progress + { C_CLEAR_DIRECTORY, "Der Inhalt von Verzeichnis {0} wird anschliessend geloescht! Moechten Sie wirklich weiterfahren ?" }, //console + { C_CONFIRM_TARGET, "Bitte bestaetigen Sie den Start des Kopiervorgangs ins Verzeichnis {0}" }, // console + { C_CONGRATULATIONS, "Gratulation!" }, // congratulations + { C_CREATE_DIRECTORY, "Das Verzeichnis {0} gibt es nicht - soll es erstellt werden ?" }, // console + { C_ENTER_TARGET_DIRECTORY, "Bitte geben Sie das Zielverzeichnis ein" }, // console + { C_ENTER_JAVA_HOME, "Bitte geben Sie das gewuenschte Java Home Verzeichnis ein (Enter fuer das aktuelle)" }, // console + { C_ENGLISH, "Englisch" }, // language + { C_EXCLUDE, "Moechten Sie Teile von der Installation ausschliessen ?" }, // installation type + { C_GENERATING_START_SCRIPTS, "Start Scripts werden generiert ..." }, // progress + { C_GERMAN, "Deutsch" }, // language + { C_INCLUDE, "Moechten Sie weitere Teile installieren ?" }, // installation type + { C_INEXCLUDE_PARTS, "Folgende Teile stehen zur Auswahl ({0} = keine weiteren)" }, // installation type + { C_INSTALL_TYPES, "Die folgenden Installationstypen sind verfuegbar:" }, // installation type + { C_INVALID_ANSWER, "Die Antwort {0} ist hier nicht gueltig" }, // error + { C_JAVA_VERSION, "Ihre Java Version fuer den Start von Jython ist: {0} / {1}" }, // version + { C_MINIMUM, "Minimum (Kern)" }, // installation type + { C_NO, "n" }, // answer + { C_NO_BIN_DIRECTORY, "Es gibt kein /bin Verzeichnis unterhalb {0}." }, //error + { C_NO_JAVA_EXECUTABLE, "Es gibt kein ausfuehrbares java in {0}." }, // error + { C_NO_VALID_JAVA, "Keine gueltige Java Version gefunden in {0}." }, // error + { C_NON_EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis {0} enthaelt bereits Daten" }, // error + { C_NOT_A_DIRECTORY, "{0} ist kein Verzeichnis. " }, // error + { C_NOT_FOUND, "{0} nicht gefunden." }, // error + { C_OS_VERSION, "Ihre Betriebssystem Version ist: {0} / {1}" }, // version + { C_OVERWRITE_DIRECTORY, "Das Verzeichnis {0} enthaelt bereits Daten, und die Installation wuerde diese ueberschreiben - ok ?" }, // console + { C_PACKING_STANDALONE_JAR, "Das standalone " + JarInstaller.JYTHON_JAR + " File wird erstellt ..." }, // progress + { C_PROCEED, "Bitte druecken Sie Enter um weiterzufahren" }, // console + { C_PROCEED_ANYWAY, "Bitte druecken Sie Enter um trotzdem weiterzufahren" }, // console + { C_READ_LICENSE, "Moechten Sie die Lizenzvereinbarung jetzt lesen ?" }, // license + { C_READ_README, "Moechten Sie den Inhalt von README jetzt lesen ?" }, // readme + { C_SCHEDULED, "{0} zur Installation vorgemerkt" }, // installation type + { C_SELECT_INSTALL_TYPE, "Bitte waehlen Sie den Installationstyp" }, // installation type + { C_SELECT_LANGUAGE, "Bitte waehlen Sie Ihre Sprache" }, // language + { C_SILENT_INSTALLATION, "Die Installation wird ohne Benutzerinteraktion ausgefuehrt" }, // installation mode + { C_STANDALONE, "Standalone (ein ausfuehrbares .jar File)" }, //installation mode + { C_STANDARD, "Standard (Kern, Bibliotheksmodule, Demos und Beispiele, Dokumentation)" }, // installation type + { C_SUCCESS, "Sie haben Jython {0} erfolgreich im Verzeichnis {1} installiert." }, // final + { C_SUMMARY, "Zusammenfassung:" }, // summary + { C_TO_CURRENT_JAVA, "Warnung: Wechsel zum aktuellen JDK wegen Fehler: {0}." }, // warning + { C_UNABLE_CREATE_DIRECTORY, "Fehler beim Erstellen von Verzeichnis {0}." }, // error + { C_UNABLE_CREATE_TMPFILE, "Fehler beim Erstellen der temporaeren Datei {0}." }, // error + { C_UNABLE_TO_DELETE, "Fehler beim Loeschen von {0}" }, // console + { C_UNSCHEDULED, "{0} von der Installation ausgeschlossen" }, // installation type + { C_UNSUPPORTED_JAVA, "Diese Java Version ist nicht unterstuetzt." }, // version + { C_UNSUPPORTED_OS, "Dieses Betriebssystem ist eventuell nicht vollstaendig unterstuetzt." }, // version + { C_USING_TYPE, "Installationstyp ist {0}" }, // installation type + { C_VERSION_INFO, "Sie sind im Begriff, Jython Version {0} zu installieren." }, // version + { C_WELCOME_TO_JYTHON, "Willkommen bei Jython !" }, // welcome + { C_YES, "j" }, // answer + + }; + + public Object[][] getContents() { + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants_en.java b/installer/src/java/org/python/util/install/TextConstants_en.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants_en.java @@ -0,0 +1,5 @@ +package org.python.util.install; + +public class TextConstants_en extends TextConstants { + // need this as placeholder +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextKeys.java b/installer/src/java/org/python/util/install/TextKeys.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextKeys.java @@ -0,0 +1,135 @@ +package org.python.util.install; + +public interface TextKeys { + public static final String ACCEPT = "ACCEPT"; + public static final String ACCEPT_PROPERTY = "ACCEPT_PROPERTY"; + public static final String ALL = "ALL"; + public static final String BROWSE = "BROWSE"; + public static final String CANCEL = "CANCEL"; + public static final String CHOOSE_LOCATION = "CHOOSE_LOCATION"; + public static final String CHOOSE_JRE = "CHOOSE_JRE"; + public static final String CONFIRM_START = "CONFIRM_START"; + public static final String CONGRATULATIONS = "CONGRATULATIONS"; + public static final String CORE = "CORE"; + public static final String CREATED_DIRECTORY = "CREATED_DIRECTORY"; + public static final String CURRENT = "CURRENT"; + public static final String CUSTOM = "CUSTOM"; + public static final String DEMOS_EXAMPLES = "DEMOS_EXAMPLES"; + public static final String DIRECTORIES_ONLY = "DIRECTORIES_ONLY"; + public static final String DOCUMENTATION = "DOCUMENTATION"; + public static final String DO_NOT_ACCEPT = "DO_NOT_ACCEPT"; + public static final String EMPTY_TARGET_DIRECTORY = "EMPTY_TARGET_DIRECTORY"; + public static final String ENGLISH = "ENGLISH"; + public static final String ERROR = "ERROR"; + public static final String ERROR_ACCESS_JARFILE = "ERROR_ACCESS_JARFILE"; + public static final String FINISH = "FINISH"; + public static final String GENERATING_START_SCRIPTS = "GENERATE_START_SCRIPTS"; + public static final String GERMAN = "GERMAN"; + public static final String INFLATING = "INFLATING"; + public static final String INFORMATION = "INFORMATION"; + public static final String INSTALLATION_CANCELLED = "INSTALLATION_CANCELLED"; + public static final String INSTALLATION_IN_PROGRESS = "INSTALLATION_IN_PROGRESS"; + public static final String INSTALLATION_TYPE_DESCRIPTION = "INSTALLATION_TYPE_DESCRIPTION"; + public static final String INSTALLATION_TYPE = "INSTALLATION_TYPE"; + public static final String JAR_NOT_FOUND = "JAR_NOT_FOUND"; + public static final String JAVA_INFO = "JAVA_INFO"; + public static final String JYTHON_INSTALL = "JYTHON_INSTALL"; + public static final String LANGUAGE_PROPERTY = "LANGUAGE_PROPERTY"; + public static final String LIBRARY_MODULES = "LIBRARY_MODULES"; + public static final String LICENSE = "LICENSE"; + public static final String MAYBE_NOT_SUPPORTED = "MAYBE_NOT_SUPPORTED"; + public static final String MINIMUM = "MINIMUM"; + public static final String NEXT = "NEXT"; + public static final String NON_EMPTY_TARGET_DIRECTORY = "NON_EMPTY_TARGET_DIRECTORY"; + public static final String NO_MANIFEST = "NO_MANIFEST"; + public static final String NOT_OK = "NOT_OK"; + public static final String OK = "OK"; + public static final String OS_INFO = "OS_INFO"; + public static final String OTHER = "OTHER"; + public static final String OVERVIEW_DESCRIPTION = "OVERVIEW_DESCRIPTION"; + public static final String OVERVIEW_TITLE = "OVERVIEW_TITLE"; + public static final String PACKING_STANDALONE_JAR = "PACKING_STANDALONE_JAR"; + public static final String PLEASE_ACCEPT_LICENSE = "PLEASE_ACCEPT_LICENSE"; + public static final String PLEASE_README = "PLEASE_README"; + public static final String PLEASE_READ_LICENSE = "PLEASE_READ_LICENSE"; + public static final String PLEASE_WAIT = "PLEASE_WAIT"; + public static final String PRESS_FINISH = "PRESS_FINISH"; + public static final String PREVIOUS = "PREVIOUS"; + public static final String PROGRESS = "PROGRESS"; + public static final String README = "README"; + public static final String SELECT = "SELECT"; + public static final String SELECT_INSTALLATION_TYPE = "SELECT_INSTALLATION_TYPE"; + public static final String SELECT_JAVA_HOME = "SELECT_JAVA_HOME"; + public static final String SELECT_LANGUAGE = "SELECT_LANGUAGE"; + public static final String SELECT_TARGET_DIRECTORY = "SELECT_TARGET_DIRECTORY"; + public static final String SOURCES = "SOURCES"; + public static final String STANDARD = "STANDARD"; + public static final String STANDALONE = "STANDALONE"; + public static final String SUCCESS = "SUCCESS"; + public static final String TARGET_DIRECTORY_PROPERTY = "TARGET_DIRECTORY_PROPERTY"; + public static final String TARGET_JAVA_HOME_PROPERTY = "TARGET_JAVA_HOME_PROPERTY"; + public static final String UNABLE_CREATE_DIRECTORY = "UNABLE_CREATE_DIRECTORY"; + public static final String UNABLE_CREATE_FILE = "UNABLE_CREATE_FILE"; + public static final String UNABLE_TO_DELETE = "UNABLE_TO_DELETE"; + public static final String UNEXPECTED_URL = "UNEXPECTED_URL"; + public static final String VERSION_INFO = "VERSION_INFO"; + public static final String WELCOME_TO_JYTHON = "WELCOME_TO_JYTHON"; + public static final String ZIP_ENTRY_SIZE = "ZIP_ENTRY_SIZE"; + public static final String ZIP_ENTRY_TOO_BIG = "ZIP_ENTRY_TOO_BIG"; + + // console texts + public static final String C_ACCEPT = "C_ACCEPT"; + public static final String C_ALL = "C_ALL"; + public static final String C_AT_ANY_TIME_CANCEL = "C_AT_ANY_TIME_CANCEL"; + public static final String C_AVAILABLE_LANGUAGES = "C_AVAILABLE_LANGUAGES"; + public static final String C_CHECK_JAVA_VERSION = "C_CHECK_JAVA_VERSION"; + public static final String C_CLEAR_DIRECTORY = "C_CLEAR_DIRECTORY"; + public static final String C_CONFIRM_TARGET = "C_CONFIRM_TARGET"; + public static final String C_CONGRATULATIONS = "C_CONGRATULATIONS"; + public static final String C_CREATE_DIRECTORY = "C_CREATE_DIRECTORY"; + public static final String C_ENGLISH = "C_ENGLISH"; + public static final String C_ENTER_TARGET_DIRECTORY = "C_ENTER_TARGET_DIRECTORY"; + public static final String C_ENTER_JAVA_HOME = "C_ENTER_JAVA_HOME"; + public static final String C_EXCLUDE = "C_EXCLUDE"; + public static final String C_GENERATING_START_SCRIPTS = "C_GENERATE_START_SCRIPTS"; + public static final String C_GERMAN = "C_GERMAN"; + public static final String C_INCLUDE = "C_INCLUDE"; + public static final String C_INEXCLUDE_PARTS = "C_INEXCLUDE_PARTS"; + public static final String C_INSTALL_TYPES = "C_INSTALL_TYPES"; + public static final String C_INVALID_ANSWER = "C_INVALID_ANSWER"; + public static final String C_JAVA_VERSION = "C_JAVA_VERSION"; + public static final String C_MINIMUM = "C_MINIMUM"; + public static final String C_NO = "C_NO"; + public static final String C_NO_BIN_DIRECTORY = "C_NO_BIN_DIRECTORY"; + public static final String C_NO_JAVA_EXECUTABLE = "C_NO_JAVA_EXECUTABLE"; + public static final String C_NO_VALID_JAVA = "C_NO_VALID_JAVA"; + public static final String C_NON_EMPTY_TARGET_DIRECTORY = "C_NON_EMPTY_TARGET_DIRECTORY"; + public static final String C_NOT_A_DIRECTORY = "C_NOT_A_DIRECTORY"; + public static final String C_NOT_FOUND = "C_NOT_FOUND"; + public static final String C_OS_VERSION = "C_OS_VERSION"; + public static final String C_OVERWRITE_DIRECTORY = "C_OVERWRITE_DIRECTORY"; + public static final String C_PACKING_STANDALONE_JAR = "C_PACKING_STANDALONE_JAR"; + public static final String C_PROCEED = "C_PROCEED"; + public static final String C_PROCEED_ANYWAY = "C_PROCEED_ANYWAY"; + public static final String C_READ_LICENSE = "C_READ_LICENSE"; + public static final String C_READ_README = "C_READ_README"; + public static final String C_SCHEDULED = "C_SCHEDULED"; + public static final String C_SELECT_INSTALL_TYPE = "C_SELECT_INSTALL_TYPE"; + public static final String C_SELECT_LANGUAGE = "C_SELECT_LANGUAGE"; + public static final String C_SILENT_INSTALLATION = "C_SILENT_INSTALLATION"; + public static final String C_STANDALONE = "C_STANDALONE"; + public static final String C_STANDARD = "C_STANDARD"; + public static final String C_SUCCESS = "C_SUCCESS"; + public static final String C_SUMMARY = "C_SUMMARY"; + public static final String C_UNABLE_CREATE_TMPFILE = "C_UNABLE_CREATE_TMPFILE"; + public static final String C_TO_CURRENT_JAVA = "C_TO_CURRENT_JAVA"; + public static final String C_UNABLE_CREATE_DIRECTORY = "C_UNABLE_CREATE_DIRECTORY"; + public static final String C_UNABLE_TO_DELETE = "C_UNABLE_TO_DELETE"; + public static final String C_UNSCHEDULED = "C_UNSCHEDULED"; + public static final String C_UNSUPPORTED_JAVA = "C_UNSUPPORTED_JAVA"; + public static final String C_UNSUPPORTED_OS = "C_UNSUPPORTED_OS"; + public static final String C_USING_TYPE = "C_USING_TYPE"; + public static final String C_VERSION_INFO = "C_VERSION_INFO"; + public static final String C_WELCOME_TO_JYTHON = "C_WELCOME_TO_JYTHON"; + public static final String C_YES = "C_YES"; +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TypePage.java b/installer/src/java/org/python/util/install/TypePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TypePage.java @@ -0,0 +1,268 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +public class TypePage extends AbstractWizardPage { + + private static final String _CUSTOM_ACTION_COMMAND = "custom"; + + private JLabel _label; + private JRadioButton _allButton; + private JRadioButton _standardButton; + private JRadioButton _minimumButton; + private JRadioButton _standaloneButton; + private JRadioButton _customButton; + + private JCheckBox _core; + private JCheckBox _mod; + private JCheckBox _demo; + private JCheckBox _doc; + private JCheckBox _src; + + private boolean _firstTime = true; + + public TypePage() { + super(); + initComponents(); + } + + private void initComponents() { + TypeChangeListener typeChangeListener = new TypeChangeListener(); + + _label = new JLabel(); + + // radio buttons + _allButton = new JRadioButton(); + _allButton.setActionCommand(Installation.ALL); + _allButton.addActionListener(typeChangeListener); + _standardButton = new JRadioButton(); + _standardButton.setActionCommand(Installation.STANDARD); + _standardButton.addActionListener(typeChangeListener); + _minimumButton = new JRadioButton(); + _minimumButton.setActionCommand(Installation.MINIMUM); + _minimumButton.addActionListener(typeChangeListener); + _standaloneButton = new JRadioButton(); + _standaloneButton.setActionCommand(Installation.STANDALONE); + _standaloneButton.addActionListener(typeChangeListener); + _customButton = new JRadioButton(); + _customButton.setActionCommand(_CUSTOM_ACTION_COMMAND); + _customButton.addActionListener(typeChangeListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_allButton); + radioButtonGroup.add(_standardButton); + radioButtonGroup.add(_minimumButton); + radioButtonGroup.add(_standaloneButton); + radioButtonGroup.add(_customButton); + JPanel radioPanel = new JPanel(new GridLayout(0, 1)); + radioPanel.add(_allButton); + radioPanel.add(_standardButton); + radioPanel.add(_minimumButton); + radioPanel.add(_standaloneButton); + radioPanel.add(_customButton); + + // check boxes + _core = new JCheckBox(); + _core.setEnabled(false); + _mod = new JCheckBox(); + _mod.setEnabled(true); + _mod.setActionCommand(InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES); + _mod.addActionListener(typeChangeListener); + _demo = new JCheckBox(); + _demo.setEnabled(true); + _demo.setActionCommand(InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES); + _demo.addActionListener(typeChangeListener); + _doc = new JCheckBox(); + _doc.setEnabled(true); + _doc.setActionCommand(InstallerCommandLine.INEXCLUDE_DOCUMENTATION); + _doc.addActionListener(typeChangeListener); + _src = new JCheckBox(); + _src.setEnabled(true); + _src.setActionCommand(InstallerCommandLine.INEXCLUDE_SOURCES); + _src.addActionListener(typeChangeListener); + + JPanel checkboxPanel = new JPanel(); + GridLayout gridLayout = new GridLayout(5, 1); + checkboxPanel.setLayout(gridLayout); + checkboxPanel.add(_core); + checkboxPanel.add(_mod); + checkboxPanel.add(_demo); + checkboxPanel.add(_doc); + checkboxPanel.add(_src); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 2; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridwidth = 1; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(radioPanel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(checkboxPanel, gridBagConstraints); + + add(panel); + } + + protected String getTitle() { + return getText(INSTALLATION_TYPE); + } + + protected String getDescription() { + return getText(INSTALLATION_TYPE_DESCRIPTION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + InstallationType installationType = getInstallationType(); + if (installationType.isAll()) { + return _allButton; + } else if (installationType.isMinimum()) { + return _minimumButton; + } else if (installationType.isStandalone()) { + return _standaloneButton; + } else if (installationType.isStandard()) { + return _standardButton; + } else { + return _customButton; + } + } + + protected void activate() { + _label.setText(getText(SELECT_INSTALLATION_TYPE) + ": "); + _allButton.setText(getText(ALL)); + _standardButton.setText(getText(STANDARD)); + _minimumButton.setText(getText(MINIMUM)); + _standaloneButton.setText(getText(STANDALONE)); + _customButton.setText(getText(CUSTOM)); + InstallationType installationType = getInstallationType(); + if (installationType.isAll()) { + _allButton.setSelected(true); + } else if (installationType.isMinimum()) { + _minimumButton.setSelected(true); + } else if (installationType.isStandalone()) { + _standaloneButton.setSelected(true); + } else if (installationType.isStandard()) { + _standardButton.setSelected(true); + } else { + _customButton.setSelected(true); + } + _core.setText(getText(CORE)); + _mod.setText(getText(LIBRARY_MODULES)); + _demo.setText(getText(DEMOS_EXAMPLES)); + _doc.setText(getText(DOCUMENTATION)); + _src.setText(getText(SOURCES)); + setCheckboxes(installationType); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private InstallationType getInstallationType() { + InstallationType installationType; + if (_firstTime) { + _firstTime = false; + installationType = new InstallationType(); + installationType.setStandard(); + FrameInstaller.setInstallationType(installationType); + } + installationType = FrameInstaller.getInstallationType(); + return installationType; + } + + private final class TypeChangeListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + InstallationType installationType = FrameInstaller.getInstallationType(); + String actionCommand = e.getActionCommand(); + if (Installation.ALL.equals(actionCommand)) { + installationType.setAll(); + setCheckboxes(installationType); + } else if (Installation.STANDARD.equals(actionCommand)) { + installationType.setStandard(); + setCheckboxes(installationType); + } else if (Installation.MINIMUM.equals(actionCommand)) { + installationType.setMinimum(); + setCheckboxes(installationType); + } else if (Installation.STANDALONE.equals(actionCommand)) { + installationType.setStandalone(); + setCheckboxes(installationType); + } else if (_CUSTOM_ACTION_COMMAND.equals(actionCommand)) { + _mod.setEnabled(true); + _demo.setEnabled(true); + _doc.setEnabled(true); + _src.setEnabled(true); + } else { + boolean selected = ((JCheckBox) e.getSource()).isSelected(); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(actionCommand)) { + if (selected) { + installationType.addLibraryModules(); + } else { + installationType.removeLibraryModules(); + } + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(actionCommand)) { + if (selected) { + installationType.addDemosAndExamples(); + } else { + installationType.removeDemosAndExamples(); + } + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(actionCommand)) { + if (selected) { + installationType.addDocumentation(); + } else { + installationType.removeDocumentation(); + } + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(actionCommand)) { + if (selected) { + installationType.addSources(); + } else { + installationType.removeSources(); + } + } + } + FrameInstaller.setInstallationType(installationType); + } + } + + void setCheckboxes(InstallationType installationType) { + _core.setSelected(true); + _mod.setSelected(installationType.installLibraryModules()); + _demo.setSelected(installationType.installDemosAndExamples()); + _doc.setSelected(installationType.installDocumentation()); + _src.setSelected(installationType.installSources()); + _standaloneButton.setSelected(installationType.isStandalone()); + _mod.setEnabled(!installationType.isPredefined()); + _demo.setEnabled(!installationType.isPredefined()); + _doc.setEnabled(!installationType.isPredefined()); + _src.setEnabled(!installationType.isPredefined()); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/UnicodeSequences.java b/installer/src/java/org/python/util/install/UnicodeSequences.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/UnicodeSequences.java @@ -0,0 +1,19 @@ +package org.python.util.install; + +/** + * Some unicode special characters + * + * @see http://www.unicode.org/charts/PDF/U0080.pdf + */ +public interface UnicodeSequences { + + public static final String A2 = "\u00C4"; // German A umlaut: A with two dots above + public static final String a2 = "\u00E4"; // German a umlaut: a with two dots above + + public static final String O2 = "\u00D6"; // German O umlaut: O with two dots above + public static final String o2 = "\u00F6"; // German o umlaut: o with two dots above + + public static final String U2 = "\u00DC"; // German U umlaut: U with two dots above + public static final String u2 = "\u00FC"; // German u umlaut: u with two dots above + +} diff --git a/installer/src/java/org/python/util/install/ValidationEvent.java b/installer/src/java/org/python/util/install/ValidationEvent.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationEvent.java @@ -0,0 +1,14 @@ +package org.python.util.install; + +import java.util.EventObject; + +public class ValidationEvent extends EventObject { + + public ValidationEvent(AbstractWizardPage source) { + super(source); + } + + public AbstractWizardPage getWizardPage() { + return (AbstractWizardPage) source; + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationException.java b/installer/src/java/org/python/util/install/ValidationException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationException.java @@ -0,0 +1,19 @@ +package org.python.util.install; + +public class ValidationException extends Exception { + public ValidationException() { + super(); + } + + public ValidationException(String message) { + super(message); + } + + public ValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationInformationException.java b/installer/src/java/org/python/util/install/ValidationInformationException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationInformationException.java @@ -0,0 +1,21 @@ +package org.python.util.install; + +public class ValidationInformationException extends Exception { + + public ValidationInformationException() { + super(); + } + + public ValidationInformationException(String message) { + super(message); + } + + public ValidationInformationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationInformationException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationListener.java b/installer/src/java/org/python/util/install/ValidationListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationListener.java @@ -0,0 +1,11 @@ +package org.python.util.install; + +public interface ValidationListener { + public void validationFailed(ValidationEvent event, ValidationException exception); + + public void validationInformationRequired(ValidationEvent event, ValidationInformationException exception); + + public void validationStarted(ValidationEvent event); + + public void validationSucceeded(ValidationEvent event); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/Wizard.java b/installer/src/java/org/python/util/install/Wizard.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/Wizard.java @@ -0,0 +1,90 @@ +package org.python.util.install; + +import java.awt.Dimension; +import java.awt.Rectangle; + +import javax.swing.JOptionPane; + +import org.python.util.install.driver.Autotest; + +public class Wizard extends AbstractWizard implements TextKeys { + + public Wizard(JarInfo jarInfo, Autotest autotest) { + super(); + + setTitle(getText(JYTHON_INSTALL)); + + LanguagePage languagePage = new LanguagePage(jarInfo); + LicensePage licensePage = new LicensePage(jarInfo); + licensePage.setValidator(new LicensePageValidator(licensePage)); + TypePage typePage = new TypePage(); + DirectorySelectionPage directoryPage = new DirectorySelectionPage(jarInfo); + directoryPage.setValidator(new DirectorySelectionPageValidator(directoryPage)); + JavaSelectionPage javaPage = new JavaSelectionPage(); + javaPage.setValidator(new JavaSelectionPageValidator(javaPage)); + OverviewPage overviewPage = new OverviewPage(); + ProgressPage progressPage = new ProgressPage(jarInfo, autotest); + ReadmePage readmePage = new ReadmePage(jarInfo); + SuccessPage successPage = new SuccessPage(jarInfo); + + this.addPage(languagePage); + this.addPage(licensePage); + this.addPage(typePage); + this.addPage(directoryPage); + this.addPage(javaPage); + this.addPage(overviewPage); + this.addPage(progressPage); + this.addPage(readmePage); + this.addPage(successPage); + + setSize(720, 330); + centerOnScreen(); + validate(); + } + + protected boolean finish() { + return true; + } + + protected String getCancelString() { + return getText(CANCEL); + } + + protected String getFinishString() { + return getText(FINISH); + } + + protected String getNextString() { + return getText(NEXT); + } + + protected String getPreviousString() { + return getText(PREVIOUS); + } + + public void validationStarted(ValidationEvent event) { + } + + public void validationFailed(ValidationEvent event, ValidationException exception) { + JOptionPane.showMessageDialog(this, exception.getMessage(), getText(TextKeys.ERROR), JOptionPane.ERROR_MESSAGE); + } + + public void validationInformationRequired(ValidationEvent event, ValidationInformationException exception) { + JOptionPane.showMessageDialog(this, exception.getMessage(), getText(INFORMATION), + JOptionPane.INFORMATION_MESSAGE); + } + + public void validationSucceeded(ValidationEvent event) { + } + + private final String getText(String textKey) { + return Installation.getText(textKey); + } + + private void centerOnScreen() { + Dimension dim = getToolkit().getScreenSize(); + Rectangle rectBounds = getBounds(); + setLocation((dim.width - rectBounds.width) / 2, (dim.height - rectBounds.height) / 2); + } + +} diff --git a/installer/src/java/org/python/util/install/WizardEvent.java b/installer/src/java/org/python/util/install/WizardEvent.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardEvent.java @@ -0,0 +1,13 @@ +package org.python.util.install; + +import java.util.EventObject; + +public class WizardEvent extends EventObject { + public WizardEvent(AbstractWizard source) { + super(source); + } + + public AbstractWizard getWizard() { + return (AbstractWizard) source; + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/WizardHeader.java b/installer/src/java/org/python/util/install/WizardHeader.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardHeader.java @@ -0,0 +1,89 @@ +package org.python.util.install; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JSeparator; + +public class WizardHeader extends AbstractWizardHeader { + private static final Dimension _iconSize = new Dimension(100, 60); + + private JLabel _descriptionLabel; + private JSeparator _headerSeparator; + private JLabel _iconLabel; + private JLabel _titleLabel; + + WizardHeader() { + super(); + initComponents(); + } + + private void initComponents() { + GridBagConstraints gridBagConstraints; + + _titleLabel = new JLabel(); + _descriptionLabel = new JLabel(); + _iconLabel = new JLabel(); + _headerSeparator = new JSeparator(); + + setLayout(new GridBagLayout()); + + setBackground(new Color(255, 255, 255)); + _titleLabel.setFont(_titleLabel.getFont().deriveFont(Font.BOLD, 14f)); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new Insets(2, 2, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + add(_titleLabel, gridBagConstraints); + + _descriptionLabel.setFont(_descriptionLabel.getFont().deriveFont(Font.PLAIN)); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(2, 7, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + add(_descriptionLabel, gridBagConstraints); + + _iconLabel.setMinimumSize(_iconSize); + _iconLabel.setMaximumSize(_iconSize); + _iconLabel.setPreferredSize(_iconSize); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 2; + gridBagConstraints.insets = new Insets(2, 2, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHEAST; + add(_iconLabel, gridBagConstraints); + + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1.0; + add(_headerSeparator, gridBagConstraints); + } + + protected void setDescription(String description) { + _descriptionLabel.setText(description); + } + + protected void setIcon(ImageIcon icon) { + _iconLabel.setIcon(icon); + } + + protected void setTitle(String title) { + _titleLabel.setText(title); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/WizardListener.java b/installer/src/java/org/python/util/install/WizardListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardListener.java @@ -0,0 +1,13 @@ +package org.python.util.install; + +public interface WizardListener { + public void wizardCancelled(WizardEvent event); + + public void wizardFinished(WizardEvent event); + + public void wizardNext(WizardEvent event); + + public void wizardPrevious(WizardEvent event); + + public void wizardStarted(WizardEvent event); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/driver/Autotest.java b/installer/src/java/org/python/util/install/driver/Autotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Autotest.java @@ -0,0 +1,274 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; + +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.InstallationListener; +import org.python.util.install.InstallerCommandLine; +import org.python.util.install.JavaHomeHandler; +import org.python.util.install.JavaVersionTester; +import org.python.util.install.Installation.JavaVersionInfo; + +public abstract class Autotest implements InstallationListener { + + private static final String _DIR_SUFFIX = "_dir"; + + private static int _count = 0; // unique test number + private static File _rootDirectory = null; + private static JavaVersionInfo _systemDefaultJavaVersion; + + private String _name; + private File _targetDir; + private JavaHomeHandler _javaHomeHandler; + private boolean _verbose; + private String[] _commandLineArgs; + private Verifier _verifier; + + /** + * constructor + * + * @throws IOException + * @throws DriverException + */ + protected Autotest(InstallerCommandLine commandLine) throws IOException, DriverException { + _count++; + buildName(); + if (_rootDirectory == null) { + createRootDirectory(); + } + createTargetDirectory(); + setCommandLineArgs(new String[0]); // a priori value + _verbose = commandLine.hasVerboseOption(); + _javaHomeHandler = commandLine.getJavaHomeHandler(); + } + + /** + * @return the root directory for all test installations + */ + protected static File getRootDir() { + return _rootDirectory; + } + + /** + * @return the target directory of this test + */ + protected File getTargetDir() { + return _targetDir; + } + + /** + * @return the name of this test + */ + protected String getName() { + return _name; + } + + /** + * @return the array of command line arguments + */ + protected String[] getCommandLineArgs() { + return _commandLineArgs; + } + + /** + * set the array of command line arguments + * + * @param commandLineArgs + */ + protected void setCommandLineArgs(String[] commandLineArgs) { + _commandLineArgs = commandLineArgs; + } + + /** + * @return the java home handler, can be asked for deviation using isDeviation(). + */ + protected JavaHomeHandler getJavaHomeHandler() { + return _javaHomeHandler; + } + + /** + * @return true if this test should be verbose + */ + protected boolean isVerbose() { + return _verbose; + } + + /** + * @return the name suffix for this test + */ + protected abstract String getNameSuffix(); + + /** + * @throws DriverException if the target directory does not exist or is empty (installation failed) + */ + protected void assertTargetDirNotEmpty() throws DriverException { + File targetDir = getTargetDir(); + if (targetDir != null) { + if (targetDir.exists() && targetDir.isDirectory()) { + if (targetDir.listFiles().length > 0) { + return; + } + } + } + throw new DriverException("installation failed for " + targetDir.getAbsolutePath()); + } + + /** + * Convenience method to add the additional arguments, if specified behind the -A option + *

+ * This adds (if present): + *

    + *
  • target directory (should always be present) + *
  • verbose + *
  • jre + *
+ */ + protected void addAdditionalArguments() { + if (getTargetDir() != null) { + addArgument("-d"); + addArgument(getTargetDir().getAbsolutePath()); + } + if (isVerbose()) { + addArgument("-v"); + } + JavaHomeHandler javaHomeHandler = getJavaHomeHandler(); + if (javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome()) { + addArgument("-j"); + addArgument(javaHomeHandler.getHome().getAbsolutePath()); + } + } + + /** + * Add an additional String argument to the command line arguments + * + * @param newArgument + */ + protected void addArgument(String newArgument) { + setCommandLineArgs(addArgument(newArgument, getCommandLineArgs())); + } + + /** + * set the verifier + */ + protected void setVerifier(Verifier verifier) { + _verifier = verifier; + _verifier.setTargetDir(getTargetDir()); + } + + protected Verifier getVerifier() { + return _verifier; + } + + // + // private stuff + // + + /** + * build a test name containing some special characters (which will be used to create the target + * directory), and store it into _name. + */ + private void buildName() { + StringBuilder b = new StringBuilder(24); + if (_count <= 99) { + b.append('0'); + } + if (_count <= 9) { + b.append('0'); + } + b.append(_count); + // explicitly use a blank, to nail down some platform specific problems + b.append(' '); + // add an exclamation mark if possible (see issue #1208) + if(canHandleExclamationMarks()) { + b.append('!'); + } + b.append(getNameSuffix()); + b.append('_'); + _name = b.toString(); + } + + /** + * Add the new argument to the args array + * + * @param newArgument + * @param args + * + * @return the new String array, with size increased by 1 + */ + private String[] addArgument(String newArgument, String[] args) { + String[] newArgs = new String[args.length + 1]; + for (int i = 0; i < args.length; i++) { + newArgs[i] = args[i]; + } + newArgs[args.length] = newArgument; + return newArgs; + } + + /** + * create the root directory for all automatic installations + *

+ * assumed to be a subdirectory of java.io.tmpdir + * + * @throws IOException + * @throws DriverException + */ + private void createRootDirectory() throws IOException, DriverException { + File tmpFile = File.createTempFile("jython.autoinstall.root_", _DIR_SUFFIX); + if (FileHelper.createTempDirectory(tmpFile)) { + _rootDirectory = tmpFile; + } else { + throw new DriverException("unable to create root temporary directory"); + } + } + + /** + * create a target directory for a test installation + * + * @throws IOException + * @throws DriverException + */ + private void createTargetDirectory() throws IOException, DriverException { + File tmpFile = File.createTempFile(getName(), _DIR_SUFFIX, _rootDirectory); + if (FileHelper.createTempDirectory(tmpFile)) { + _targetDir = tmpFile; + } else { + throw new DriverException("unable to create temporary target directory"); + } + } + + /** + * Determine if the target directory may contain an exclamation mark (see also issue #1208). + *

+ * Autotests can handle exclamation marks, if both the running and the system default java + * specification versions are 1.6 or higher. Class.getResource() was fixed for JDK 1.6, but only if the directory name does not end with '!'... + *

+ * Currently there is no way on windows, because the enabledelayedexpansion in jython.bat cannot + * handle exclamation marks in variable names. + * + * @return true if we can handle exclamation marks, false otherwise + */ + private boolean canHandleExclamationMarks() { + boolean exclamation = false; + if (!Installation.isWindows()) { + // get the running java specification version + String specificationVersion = System.getProperty(JavaVersionTester.JAVA_SPECIFICATION_VERSION, + ""); + if (Installation.getJavaSpecificationVersion(specificationVersion) > 15) { + // get the system default java version + if (_systemDefaultJavaVersion == null) { + _systemDefaultJavaVersion = Installation.getDefaultJavaVersion(); + } + if (_systemDefaultJavaVersion.getErrorCode() == Installation.NORMAL_RETURN) { + specificationVersion = _systemDefaultJavaVersion.getSpecificationVersion(); + if (Installation.getJavaSpecificationVersion(specificationVersion) > 15) { + exclamation = true; + } + } + } + } + return exclamation; + } + +} diff --git a/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java b/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java @@ -0,0 +1,30 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.python.util.install.InstallerCommandLine; + +public class ConsoleAutotest extends SilentAutotest { + + private Collection _answers; + + protected ConsoleAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + _answers = new ArrayList(50); + } + + protected void addAnswer(String answer) { + _answers.add(answer); + } + + protected Collection getAnswers() { + return _answers; + } + + protected String getNameSuffix() { + return "consoleTest"; + } + +} diff --git a/installer/src/java/org/python/util/install/driver/ConsoleDriver.java b/installer/src/java/org/python/util/install/driver/ConsoleDriver.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/ConsoleDriver.java @@ -0,0 +1,58 @@ +package org.python.util.install.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collection; +import java.util.Iterator; + +/** + * A class driving another class, while the other class is performing console I/O. + * + *

+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * 
+ */ +public class ConsoleDriver extends Thread { + + private Tunnel _tunnel; + private Collection _answers; + + public ConsoleDriver(Tunnel tunnel, Collection answers) { + _tunnel = tunnel; + _answers = answers; + } + + /** + * Send answers in the correct sequence, as soon as the question is asked. + */ + public void run() { + Iterator answersIterator = _answers.iterator(); + while (answersIterator.hasNext()) { + String answer = (String) answersIterator.next(); + try { + readLine(); + sendAnswer(answer); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private void sendAnswer(String answer) throws IOException, InterruptedException { + Thread.sleep(100); // wait to be sure the question is really issued on the other end of the tunnel + System.out.println(" -> driving: '" + answer + "'"); + answer += Tunnel.NEW_LINE; + _tunnel.getAnswerSenderStream().write(answer.getBytes()); + _tunnel.getAnswerSenderStream().flush(); + } + + private void readLine() throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(_tunnel.getQuestionReceiverStream())); + reader.readLine(); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/DriverException.java b/installer/src/java/org/python/util/install/driver/DriverException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/DriverException.java @@ -0,0 +1,17 @@ +package org.python.util.install.driver; + +public class DriverException extends Exception { + + public DriverException() { + super(); + } + + public DriverException(String message) { + super(message); + } + + public DriverException(Throwable cause) { + super(cause); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/GuiAutotest.java b/installer/src/java/org/python/util/install/driver/GuiAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/GuiAutotest.java @@ -0,0 +1,188 @@ +package org.python.util.install.driver; + +import java.awt.AWTException; +import java.awt.Robot; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.python.util.install.InstallerCommandLine; + +public class GuiAutotest extends Autotest { + + private static final int _DEFAULT_DELAY = 500; // ms + + private Robot _robot; + private List _keyActions; + private boolean _waiting = false; + + protected GuiAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + _keyActions = new ArrayList(); + // pass in the target directory, verbositiy + String[] args = new String[] { "-d", getTargetDir().getAbsolutePath() }; + setCommandLineArgs(args); + addAdditionalArguments(); + } + + protected String getNameSuffix() { + return "guiTest"; + } + + /** + * add a normal key action (press and release) + * + * @param keyCode + */ + protected void addKeyAction(int keyCode) { + KeyAction keyAction = new KeyAction(keyCode); + addKeyAction(keyAction); + } + + /** + * add a normal key action (press and release), with a specific delay + * + * @param keyCode + * @param delay + */ + protected void addKeyAction(int keyCode, int delay) { + KeyAction keyAction = new KeyAction(keyCode, delay); + addKeyAction(keyAction); + } + + /** + * add a key action (press and release) which waits before executing + * + * @param keyCode + */ + protected void addWaitingKeyAction(int keyCode) { + KeyAction keyAction = new KeyAction(keyCode, true); + addKeyAction(keyAction); + } + + protected void setWaiting(boolean waiting) { + _waiting = waiting; + } + + /** + * execute a single gui auto test + * + * @throws DriverException + */ + protected void execute() throws DriverException { + try { + _robot = new Robot(); + + System.out.println("waiting 2 seconds for the first gui ... please do not change focus"); + _robot.delay(2000); // initial gui load + + Iterator actionsIterator = _keyActions.iterator(); + while (actionsIterator.hasNext()) { + KeyAction keyAction = (KeyAction) actionsIterator.next(); + setWaiting(keyAction.isWait()); + if (isWaiting()) { + System.out.println("waiting for the installation to finish ..."); + } + while (isWaiting()) { + try { + Thread.sleep(_DEFAULT_DELAY); + } catch (InterruptedException e) { + throw new DriverException(e); + } + } + executeKeyAction(keyAction); + } + } catch (AWTException ae) { + throw new DriverException(ae); + } + + } + + /** + * General KeyAction + */ + protected static class KeyAction { + private int _keyCode; + private int _delay; + private boolean _wait; + + /** + * @param keyCode + */ + protected KeyAction(int keyCode) { + this(keyCode, _DEFAULT_DELAY); + } + + /** + * @param keyCode + * @param delay in ms + */ + protected KeyAction(int keyCode, int delay) { + super(); + setKeyCode(keyCode); + setDelay(delay); + } + + /** + * @param keyCode + * @param wait true if we should wait before executing this key action + */ + protected KeyAction(int keyCode, boolean wait) { + this(keyCode, _DEFAULT_DELAY); + setWait(wait); + } + + protected void setKeyCode(int keyCode) { + _keyCode = keyCode; + } + + protected void setDelay(int delay) { + _delay = delay; + } + + protected int getDelay() { + return _delay; + } + + protected int getKeyCode() { + return _keyCode; + } + + protected void setWait(boolean wait) { + _wait = wait; + } + + protected boolean isWait() { + return _wait; + } + } + + // + // interface InstallationListener + // + + public void progressFinished() { + setWaiting(false); + } + + // + // private stuff + // + + private boolean isWaiting() { + return _waiting; + } + + private void addKeyAction(KeyAction keyAction) { + _keyActions.add(keyAction); + } + + private void executeKeyAction(KeyAction keyAction) { + _robot.delay(keyAction.getDelay()); + _robot.keyPress(keyAction.getKeyCode()); + _robot.delay(20); // delay was handled before press + _robot.keyRelease(keyAction.getKeyCode()); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/InstallationDriver.java b/installer/src/java/org/python/util/install/driver/InstallationDriver.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/InstallationDriver.java @@ -0,0 +1,416 @@ +package org.python.util.install.driver; + +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.python.util.install.ConsoleInstaller; +import org.python.util.install.Installation; +import org.python.util.install.InstallerCommandLine; +import org.python.util.install.JavaHomeHandler; + +public class InstallationDriver { + private SilentAutotest[] _silentTests; + private ConsoleAutotest[] _consoleTests; + private GuiAutotest[] _guiTests; + private InstallerCommandLine _commandLine; + + /** + * construct the driver + * + * @param commandLine the console arguments + * + * @throws IOException + * @throws DriverException + */ + public InstallationDriver(InstallerCommandLine commandLine) throws DriverException { + _commandLine = commandLine; + try { + buildSilentTests(); + buildConsoleTests(); + buildGuiTests(); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * execute all the automatic tests + * + * @throws DriverException + */ + public void drive() throws DriverException { + try { + // silent tests + for (int i = 0; i < _silentTests.length; i++) { + driveSilentTest(_silentTests[i]); + } + // console tests + for (int i = 0; i < _consoleTests.length; i++) { + driveConsoleTest(_consoleTests[i]); + } + // gui tests + for (int i = 0; i < _guiTests.length; i++) { + driveGuiTest(_guiTests[i]); + } + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * execute a single console test + * + * @param consoleTest + * @throws DriverException + * @throws IOException + * @throws IOException + */ + private void driveConsoleTest(ConsoleAutotest consoleTest) throws DriverException, IOException { + Tunnel _tunnel; + _tunnel = new Tunnel(); + // have to fork off the driver thread first + ConsoleDriver driver = new ConsoleDriver(_tunnel, consoleTest.getAnswers()); + driver.start(); + // now do the installation + Installation.driverMain(consoleTest.getCommandLineArgs(), consoleTest, _tunnel); + _tunnel.close(); + validate(consoleTest); + } + + /** + * execute a single silent test + * + * @param silentTest + * @throws DriverException + */ + private void driveSilentTest(SilentAutotest silentTest) throws DriverException { + Installation.driverMain(silentTest.getCommandLineArgs(), silentTest, null); // only a thin wrapper + validate(silentTest); + } + + /** + * execute a single gui test + * + * @param guiTest + * @throws DriverException + */ + private void driveGuiTest(GuiAutotest guiTest) throws DriverException { + Installation.driverMain(guiTest.getCommandLineArgs(), guiTest, null); // only a thin wrapper + guiTest.execute(); + validate(guiTest); + } + + /** + * perform validations after the test was run + * + * @param autoTest + * @throws DriverException + */ + private void validate(Autotest autoTest) throws DriverException { + autoTest.assertTargetDirNotEmpty(); + if (autoTest.getVerifier() != null) { + System.out.println("verifying installation - this can take a while ..."); + autoTest.getVerifier().verify(); + System.out.println("... installation ok.\n"); + } + } + + /** + * @return the command line the autotest session was started with + */ + private InstallerCommandLine getOriginalCommandLine() { + return _commandLine; + } + + /** + * build all the silent tests + * + * @throws IOException + * @throws DriverException + */ + private void buildSilentTests() throws IOException, DriverException { + List silentTests = new ArrayList(50); + + SilentAutotest test1 = new SilentAutotest(getOriginalCommandLine()); + String[] arguments = new String[] { "-s" }; + test1.setCommandLineArgs(arguments); + test1.addAdditionalArguments(); // this also adds target directory + test1.setVerifier(new NormalVerifier()); + silentTests.add(test1); + + SilentAutotest test2 = new SilentAutotest(getOriginalCommandLine()); + arguments = new String[] { "-s", "-t", "minimum" }; + test2.setCommandLineArgs(arguments); + test2.addAdditionalArguments(); + test2.setVerifier(new NormalVerifier()); + silentTests.add(test2); + + SilentAutotest test3 = new SilentAutotest(getOriginalCommandLine()); + arguments = new String[] { "-s", "-t", "standalone" }; + test3.setCommandLineArgs(arguments); + test3.addAdditionalArguments(); + test3.setVerifier(new StandaloneVerifier()); + silentTests.add(test3); + + // build array + int size = silentTests.size(); + _silentTests = new SilentAutotest[size]; + Iterator silentIterator = silentTests.iterator(); + for (int i = 0; i < size; i++) { + _silentTests[i] = silentIterator.next(); + } + } + + /** + * build all the console tests + * + * @throws IOException + * @throws DriverException + */ + private void buildConsoleTests() throws IOException, DriverException { + List consoleTests = new ArrayList(5); + final String[] arguments; + if (getOriginalCommandLine().hasVerboseOption()) { + arguments = new String[] { "-c", "-v" }; + } else { + arguments = new String[] { "-c" }; + } + // do NOT call addAdditionalArguments() + + ConsoleAutotest test1 = new ConsoleAutotest(getOriginalCommandLine()); + test1.setCommandLineArgs(arguments); + test1.addAnswer("e"); // language + test1.addAnswer("n"); // no read of license + test1.addAnswer("y"); // accept license + test1.addAnswer("3"); // type: minimum + test1.addAnswer("n"); // include: nothing + test1.addAnswer(test1.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test1); + test1.addAnswer("y"); // confirm copying + test1.addAnswer("n"); // no readme + test1.setVerifier(new NormalVerifier()); + consoleTests.add(test1); + + ConsoleAutotest test2 = new ConsoleAutotest(getOriginalCommandLine()); + test2.setCommandLineArgs(arguments); + test2.addAnswer("e"); // language + test2.addAnswer("n"); // no read of license + test2.addAnswer("y"); // accept license + test2.addAnswer("3"); // type: minimum + test2.addAnswer("y"); // include + test2.addAnswer("mod"); // include + test2.addAnswer("demo"); // include + test2.addAnswer("src"); // include + test2.addAnswer("n"); // no further includes + test2.addAnswer("y"); // exclude + test2.addAnswer("demo"); // exclude + test2.addAnswer("wrongAnswer"); // wrong answer + test2.addAnswer("n"); // no further excludes + test2.addAnswer(test2.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test2); + test2.addAnswer("y"); // confirm copying + test2.addAnswer("n"); // no readme + test2.setVerifier(new NormalVerifier()); + consoleTests.add(test2); + + ConsoleAutotest test3 = new ConsoleAutotest(getOriginalCommandLine()); + test3.setCommandLineArgs(arguments); + test3.addAnswer("e"); // language + test3.addAnswer("n"); // no read of license + test3.addAnswer("y"); // accept license + test3.addAnswer("9"); // type: standalone + test3.addAnswer(test3.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test3); + test3.addAnswer("y"); // confirm copying + test3.addAnswer("n"); // no readme + test3.setVerifier(new StandaloneVerifier()); + consoleTests.add(test3); + + // test for bug 1783960 + ConsoleAutotest test4 = new ConsoleAutotest(getOriginalCommandLine()); + test4.setCommandLineArgs(arguments); + test4.addAnswer("e"); // language + test4.addAnswer("n"); // no read of license + test4.addAnswer("y"); // accept license + test4.addAnswer("2"); // type: standard + test4.addAnswer("n"); // no includes + test4.addAnswer("y"); // exclude + test4.addAnswer("n"); // no further excludes + test4.addAnswer(test4.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test4); + test4.addAnswer("y"); // confirm copying + test4.addAnswer("n"); // no readme + test4.setVerifier(new NormalVerifier()); + consoleTests.add(test4); + + // build array + int size = consoleTests.size(); + _consoleTests = new ConsoleAutotest[size]; + Iterator consoleIterator = consoleTests.iterator(); + for (int i = 0; i < size; i++) { + _consoleTests[i] = consoleIterator.next(); + } + } + + private void addJavaAndOSAnswers(ConsoleAutotest test) { + JavaHomeHandler javaHomeHandler = test.getJavaHomeHandler(); + if (javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome()) { + test.addAnswer(javaHomeHandler.getHome().getAbsolutePath()); // different jre + } else { + test.addAnswer(ConsoleInstaller.CURRENT_JRE); + } + if (!Installation.isValidOs()) { + test.addAnswer(""); // enter to proceed anyway + } + } + + /** + * build all the gui tests + * + * @throws IOException + * @throws DriverException + */ + private void buildGuiTests() throws IOException, DriverException { + List guiTests = new ArrayList(50); + + if (Installation.isGuiAllowed()) { + GuiAutotest guiTest1 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest1); + // type page - use 'Standard' + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest1); + guiTest1.setVerifier(new NormalVerifier()); + guiTests.add(guiTest1); + + GuiAutotest guiTest2 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest2); + // type page - use 'All' + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_SPACE); // select 'All' + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest2); + guiTest2.setVerifier(new NormalVerifier()); + guiTests.add(guiTest2); + + GuiAutotest guiTest3 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest3); + // type page - use 'Custom' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // select 'Custom' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // deselect 'Demos and Examples' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // deselect 'Documentation' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // select 'Sources' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest3); + guiTest3.setVerifier(new NormalVerifier()); + guiTests.add(guiTest3); + + GuiAutotest guiTest4 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest4); + // type page - use 'Standalone' + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_SPACE); // select 'Standalone' + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest4); + guiTest4.setVerifier(new StandaloneVerifier()); + guiTests.add(guiTest4); + } + + // build array + int size = guiTests.size(); + _guiTests = new GuiAutotest[size]; + Iterator guiIterator = guiTests.iterator(); + for (int i = 0; i < size; i++) { + _guiTests[i] = guiIterator.next(); + } + } + + private void buildLanguageAndLicensePage(GuiAutotest guiTest) { + // language page + guiTest.addKeyAction(KeyEvent.VK_E); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // license page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); // select "i accept" + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } + + private void buildRestOfGuiPages(GuiAutotest guiTest) { + // directory page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // java selection page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + JavaHomeHandler javaHomeHandler = guiTest.getJavaHomeHandler(); + boolean isValidDeviation = javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome(); + if (isValidDeviation) { // need 2 more tabs + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + } + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // overview page + if (isValidDeviation) { + guiTest.addKeyAction(KeyEvent.VK_SPACE, 3000); // enough time to check the java version + } else { + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } + // installation page (skipped) + // readme page + guiTest.addWaitingKeyAction(KeyEvent.VK_TAB); // wait for the installation to finish + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // success page + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } +} diff --git a/installer/src/java/org/python/util/install/driver/NormalVerifier.java b/installer/src/java/org/python/util/install/driver/NormalVerifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/NormalVerifier.java @@ -0,0 +1,287 @@ +package org.python.util.install.driver; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.MessageFormat; +import java.util.StringTokenizer; + +import org.python.util.install.ChildProcess; +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.JavaHomeHandler; + +public class NormalVerifier implements Verifier { + + protected static final String AUTOTEST_PY = "autotest.py"; + + protected static final String JYTHON_TEST = "jython_test"; + + private static final String BIN = "bin"; + + private static final String BAT_EXTENSION = ".bat"; + + private static final String JYTHON_UP = "jython up and running!"; + + private static final String JYTHON = "jython"; + + private static final String TEMPLATE_SUFFIX = ".template"; + + private static final String VERIFYING = "verifying"; + + private File _targetDir; + + public void setTargetDir(File targetDir) { + _targetDir = targetDir; + } + + public File getTargetDir() { + return _targetDir; + } + + public void verify() throws DriverException { + createTestScriptFile(); // create the test .py script + // verify the most simple start of jython works + verifyStart(getSimpleCommand()); + if (doShellScriptTests()) { + // verify more complex versions of starting jython + verifyStart(getShellScriptTestCommand()); + } + } + + /** + * Will be overridden in subclass StandaloneVerifier + * + * @return the command array to start jython with + * @throws DriverException + * if there was a problem getting the target directory path + */ + protected String[] getSimpleCommand() throws DriverException { + String parentDirName = null; + try { + parentDirName = getTargetDir().getCanonicalPath() + File.separator; + } catch (IOException ioe) { + throw new DriverException(ioe); + } + String[] command = new String[2]; + if (Installation.isWindows()) { + command[0] = parentDirName + JYTHON + BAT_EXTENSION; + } else { + command[0] = parentDirName + JYTHON; + } + command[1] = parentDirName + AUTOTEST_PY; + return command; + } + + /** + * @return The command to test the shell script more deeply + * @throws DriverException + */ + protected final String[] getShellScriptTestCommand() throws DriverException { + // first we have to create the shell script + File testCommandDir = getShellScriptTestCommandDir(); + if (!testCommandDir.exists()) { + if (!testCommandDir.mkdirs()) { + throw new DriverException("unable to create directory " + + testCommandDir.getAbsolutePath()); + } + } + String commandName = JYTHON_TEST; + boolean isWindows = Installation.isWindows(); + if (isWindows) { + commandName = commandName.concat(BAT_EXTENSION); + } + File command = new File(testCommandDir, commandName); + try { + if (!command.exists()) { + command.createNewFile(); + } + FileHelper.write(command, getShellScriptTestContents()); + if (!isWindows) { + FileHelper.makeExecutable(command); + } + return new String[] {command.getCanonicalPath()}; + } catch (Exception e) { + throw new DriverException(e); + } + } + + /** + * @return The contents of the shell test script + * @throws DriverException + */ + protected final String getShellScriptTestContents() throws DriverException { + String contents = ""; + String templateName = JYTHON_TEST; + if (Installation.isWindows()) { + templateName = templateName.concat(BAT_EXTENSION); + } + templateName = templateName.concat(TEMPLATE_SUFFIX); + InputStream inputStream = FileHelper.getRelativeURLAsStream(getClass(), templateName); + if (inputStream != null) { + try { + String template = FileHelper.readAll(inputStream); + String targetDirPath = getTargetDir().getCanonicalPath(); + String upScriptPath = getSimpleCommand()[1]; + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(); + String javaHomeString = ""; + if (javaHomeHandler.isValidHome()) { + javaHomeString = javaHomeHandler.getHome().getAbsolutePath(); + } + contents = MessageFormat.format(template, + targetDirPath, + upScriptPath, + javaHomeString, + VERIFYING); + } catch (Exception e) { + throw new DriverException(e); + } + } + return contents; + } + + /** + * @return The directory where to create the shell script test command in. + * + * @throws DriverException + */ + protected final File getShellScriptTestCommandDir() throws DriverException { + String dirName; + try { + dirName = getTargetDir().getCanonicalPath().concat(File.separator).concat(BIN); + return new File(dirName); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * Internal method verifying a jython-starting command by capturing the ouptut + * + * @param command + * + * @throws DriverException + */ + private void verifyStart(String[] command) throws DriverException { + ChildProcess childProcess = new ChildProcess(command); + childProcess.setDebug(true); + ByteArrayOutputStream redirectedErr = new ByteArrayOutputStream(); + ByteArrayOutputStream redirectedOut = new ByteArrayOutputStream(); + int exitValue = 0; + PrintStream oldErr = System.err; + PrintStream oldOut = System.out; + try { + System.setErr(new PrintStream(redirectedErr)); + System.setOut(new PrintStream(redirectedOut)); + exitValue = childProcess.run(); + } finally { + System.setErr(oldErr); + System.setOut(oldOut); + } + // verify the output + String output = null; + String error = null; + try { + redirectedErr.flush(); + redirectedOut.flush(); + String encoding = "US-ASCII"; + output = redirectedOut.toString(encoding); + error = redirectedErr.toString(encoding); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + if (exitValue != 0) { + throw new DriverException("start of jython failed, output:\n" + output + "\nerror:\n" + + error); + } + verifyError(error); + verifyOutput(output); + } + + /** + * Will be overridden in subclass StandaloneVerifier + * + * @return true if the jython start shell script should be verified (using + * different options) + */ + protected boolean doShellScriptTests() { + return true; + } + + private void verifyError(String error) throws DriverException { + StringTokenizer tokenizer = new StringTokenizer(error, "\n"); + while (tokenizer.hasMoreTokens()) { + String line = tokenizer.nextToken(); + if (isExpectedError(line)) { + feedback(line); + } else { + throw new DriverException(error); + } + } + } + + private boolean isExpectedError(String line) { + boolean expected = false; + if (line.startsWith("*sys-package-mgr*")) { + expected = true; + } else if (line.indexOf("32 bit") >= 0 && line.indexOf("64 bit") >= 0) { + // OS X incompatibility message when using -A -j java1.6.0 from java1.5.0 + expected = true; + } + return expected; + } + + private void verifyOutput(String output) throws DriverException { + boolean started = false; + StringTokenizer tokenizer = new StringTokenizer(output, "\n"); + while (tokenizer.hasMoreTokens()) { + String line = tokenizer.nextToken(); + if (isExpectedOutput(line)) { + feedback(line); + if (line.startsWith(JYTHON_UP)) { + started = true; + } + } else { + throw new DriverException(output); + } + } + if (!started) { + throw new DriverException("start of jython failed:\n" + output); + } + } + + private boolean isExpectedOutput(String line) { + boolean expected = false; + if (line.startsWith("[ChildProcess]") || line.startsWith(VERIFYING)) { + expected = true; + } else if (line.startsWith(JYTHON_UP)) { + expected = true; + } + return expected; + } + + private String getTestScript() { + StringBuilder b = new StringBuilder(80); + b.append("import sys\n"); + b.append("import os\n"); + b.append("print '"); + b.append(JYTHON_UP); + b.append("'\n"); + return b.toString(); + } + + private void createTestScriptFile() throws DriverException { + File file = new File(getTargetDir(), AUTOTEST_PY); + try { + FileHelper.write(file, getTestScript()); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + private void feedback(String line) { + System.out.println(line); + } +} diff --git a/installer/src/java/org/python/util/install/driver/SilentAutotest.java b/installer/src/java/org/python/util/install/driver/SilentAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/SilentAutotest.java @@ -0,0 +1,25 @@ +package org.python.util.install.driver; + +import java.io.IOException; + +import org.python.util.install.InstallerCommandLine; + +public class SilentAutotest extends Autotest { + + protected SilentAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + } + + protected String getNameSuffix() { + return "silentTest"; + } + + // + // interface InstallationListener + // + + public void progressFinished() { + // ignored + } + +} diff --git a/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java b/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java @@ -0,0 +1,74 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.python.util.install.JavaHomeHandler; + +public class StandaloneVerifier extends NormalVerifier { + + public void verify() throws DriverException { + // make sure only JYTHON_JAR is in the target directory + if (getTargetDir().listFiles().length > 1) { + throw new DriverException("more than " + JYTHON_JAR + " installed"); + } + // make sure JYTHON_JAR contains a MANIFEST and a /Lib directory + verifyJythonJar(); + // do the jython startup verification from the superclass + super.verify(); + } + + @Override + protected String[] getSimpleCommand() throws DriverException { + String parentDirName = null; + try { + parentDirName = getTargetDir().getCanonicalPath() + File.separator; + } catch (IOException ioe) { + throw new DriverException(ioe); + } + String command[] = new String[4]; + command[0] = new JavaHomeHandler().getExecutableName(); + command[1] = "-jar"; + command[2] = parentDirName + JYTHON_JAR; + command[3] = parentDirName + AUTOTEST_PY; + return command; + } + + @Override + protected boolean doShellScriptTests() { + return false; + } + + private void verifyJythonJar() throws DriverException { + File jythonJar = getTargetDir().listFiles()[0]; + JarFile jar = null; + try { + jar = new JarFile(jythonJar); + if (jar.getManifest() == null) { + throw new DriverException(JYTHON_JAR + " contains no MANIFEST"); + } + boolean hasLibDir = false; + Enumeration entries = jar.entries(); + while (!hasLibDir && entries.hasMoreElements()) { + JarEntry entry = (JarEntry)entries.nextElement(); + if (entry.getName().startsWith("Lib/")) { + hasLibDir = true; + } + } + if (!hasLibDir) { + throw new DriverException(JYTHON_JAR + " contains no /Lib directory"); + } + } catch (IOException e) { + throw new DriverException(e); + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException ioe) {} + } + } + } +} diff --git a/installer/src/java/org/python/util/install/driver/Tunnel.java b/installer/src/java/org/python/util/install/driver/Tunnel.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Tunnel.java @@ -0,0 +1,61 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +/** + * A communication tunnel between a console driver and a console. + * + *
+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * 
+ */ +public class Tunnel { + + public static final String NEW_LINE = "\n"; + + private PipedOutputStream _questionSenderStream; + private PipedInputStream _questionReceiverStream; + private PipedOutputStream _answerSenderStream; + private PipedInputStream _answerReceiverStream; + + public Tunnel() throws IOException { + _questionSenderStream = new PipedOutputStream(); + _questionReceiverStream = new PipedInputStream(); + _questionSenderStream.connect(_questionReceiverStream); + + _answerSenderStream = new PipedOutputStream(); + _answerReceiverStream = new PipedInputStream(); + _answerSenderStream.connect(_answerReceiverStream); + } + + public PipedOutputStream getQuestionSenderStream() { + return _questionSenderStream; + } + + public PipedInputStream getQuestionReceiverStream() { + return _questionReceiverStream; + } + + public PipedOutputStream getAnswerSenderStream() { + return _answerSenderStream; + } + + public PipedInputStream getAnswerReceiverStream() { + return _answerReceiverStream; + } + + public void close() throws IOException { + _questionReceiverStream.close(); + _questionSenderStream.close(); + _answerReceiverStream.close(); + _answerSenderStream.close(); + + _questionReceiverStream = null; + _questionSenderStream = null; + _answerReceiverStream = null; + _answerSenderStream = null; + } +} diff --git a/installer/src/java/org/python/util/install/driver/Verifier.java b/installer/src/java/org/python/util/install/driver/Verifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Verifier.java @@ -0,0 +1,17 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.JarInstaller; + +public interface Verifier { + + public static final String JYTHON_JAR = JarInstaller.JYTHON_JAR; + + public void setTargetDir(File targetDir); + + public File getTargetDir(); + + public void verify() throws DriverException; + +} diff --git a/installer/src/java/org/python/util/install/driver/jython_test.bat.template b/installer/src/java/org/python/util/install/driver/jython_test.bat.template new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/jython_test.bat.template @@ -0,0 +1,73 @@ + at echo off + +rem 3 variables to be set from the caller, UNquoted: +set _INSTALL_DIR={0} +set _SCRIPT={1} +set _JAVA_HOME={2} + +rem save old home env vars and classpath: +set _OLD_JAVA_HOME=%JAVA_HOME% +set _OLD_JYTHON_HOME=%JYTHON_HOME% +set _OLD_CLASSPATH=%CLASSPATH% + +cd /d "%_INSTALL_DIR%\bin" + +echo {3}: only JAVA_HOME, quoted: +set JAVA_HOME="%_JAVA_HOME%" +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: only JAVA_HOME, UNquoted: +set JAVA_HOME=%_JAVA_HOME% +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: both homes quoted: +set JAVA_HOME="%_JAVA_HOME%" +set JYTHON_HOME="%_INSTALL_DIR%" +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: both homes UNquoted: +set JAVA_HOME=%_JAVA_HOME% +set JYTHON_HOME=%_INSTALL_DIR% +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +cd .. + +echo {3}: no home, calling in home dir: +set JAVA_HOME= +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +cd .. + +echo {3}: no home, calling /jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +call "%_INSTALL_DIR%\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: no home, calling bin/jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +call "%_INSTALL_DIR%\bin\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: no home, setting CLASSPATH, calling /jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +set CLASSPATH=.;C:\Program Files (x86)\Java\jre6\lib\ext\QTJava.zip +call "%_INSTALL_DIR%\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +rem cleanup: +set JAVA_HOME=%_OLD_JAVA_HOME% +set JYTHON_HOME=%_OLD_JYTHON_HOME% +set CLASSPATH=%_OLD_CLASSPATH% +cd /d "%~dp0%" +exit /b %E% diff --git a/installer/src/java/org/python/util/install/driver/jython_test.template b/installer/src/java/org/python/util/install/driver/jython_test.template new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/jython_test.template @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# 3 variables to be set from the caller (unquoted) -> quoted in here: +_INSTALL_DIR="{0}" +_SCRIPT="{1}" +_JAVA_HOME="{2}" + +# save old home env vars: +_OLD_JAVA_HOME=$JAVA_HOME +_OLD_JYTHON_HOME=$JYTHON_HOME +# save current dir +_CURDIR=`pwd` + +cd "$_INSTALL_DIR/bin" + +echo "{3}: no home:" +export JAVA_HOME= +export JYTHON_HOME= +./jython "$_SCRIPT" + +echo "{3}: only JAVA_HOME:" +export JAVA_HOME="$_JAVA_HOME" +export JYTHON_HOME= +./jython "$_SCRIPT" + +echo "{3}: only JYTHON_HOME:" +export JAVA_HOME= +export JYTHON_HOME="$_INSTALL_DIR" +./jython "$_SCRIPT" + +echo "{3}: both homes:" +export JAVA_HOME="$_JAVA_HOME" +export JYTHON_HOME="$_INSTALL_DIR" +./jython "$_SCRIPT" + +cd .. + +echo "{3}: no home, calling in home dir:" +export JAVA_HOME= +export JYTHON_HOME= +./jython "$_SCRIPT" + +cd ~ + +echo "{3}: no home, calling /jython from another working dir:" +export JAVA_HOME= +export JYTHON_HOME= +"$_INSTALL_DIR/jython" "$_SCRIPT" + +echo "{3}: no home, calling /bin/jython from another working dir:" +export JAVA_HOME= +export JYTHON_HOME= +"$_INSTALL_DIR/bin/jython" "$_SCRIPT" + +# cleanup: +cd "$_CURDIR" +export JAVA_HOME=$_OLD_JAVA_HOME +export JYTHON_HOME=$_OLD_JYTHON_HOME diff --git a/installer/src/java/org/python/util/install/jython_small_c.png b/installer/src/java/org/python/util/install/jython_small_c.png new file mode 100644 index 0000000000000000000000000000000000000000..e65a7f18e5ecad694dd2093cf01256a2eb93aada GIT binary patch [stripped] diff --git a/installer/test/java/org/AllTests.java b/installer/test/java/org/AllTests.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/AllTests.java @@ -0,0 +1,101 @@ +package org; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * A package recursive test suite. + *

+ * All classes ending with 'Test' are added to the suite. + * + * @see AllTests.TestClassFilter + */ +public class AllTests extends TestSuite { + + /** + * @return Test suite at the directory where this class resists + * + * @throws Exception + */ + public static Test suite() throws Exception { + Class suiteClass = AllTests.class; + String testSuiteClassName = suiteClass.getName(); + File suiteFile = new File(suiteClass.getClassLoader().getResource( + testSuiteClassName.replace('.', '/').concat(".class")).getFile()); + String basePackage = suiteClass.getPackage().getName(); + File baseDir = suiteFile.getParentFile(); + TestSuite suite = new TestSuite("Test " + basePackage + " recursive."); + buildSuite(baseDir.getAbsolutePath().length(), basePackage, baseDir, new TestClassFilter(), suite); + return suite; + } + + // + // private methods + // + + private static void buildSuite(int prefixLength, String basePackage, File currentDir, FilenameFilter filter, + TestSuite currentSuite) throws Exception { + List potentialDirectories = Arrays.asList(currentDir.listFiles(filter)); + if (potentialDirectories.size() == 0) { + return; + } + StringBuffer currentPackageName = new StringBuffer(200); + currentPackageName.append(basePackage); + currentPackageName.append(currentDir.getAbsolutePath().substring(prefixLength).replace('\\', '.').replace('/', + '.')); + + List classFiles = new ArrayList(potentialDirectories.size()); + Collections.sort(potentialDirectories, new FileComparator()); + Iterator directoryIterator = potentialDirectories.iterator(); + while (directoryIterator.hasNext()) { + File potentialDirectory = (File) directoryIterator.next(); + if (potentialDirectory.isDirectory()) { + TestSuite subTestSuite = new TestSuite(potentialDirectory.getName()); + buildSuite(prefixLength, basePackage, potentialDirectory, filter, subTestSuite); + // only if suite contains tests + if (subTestSuite.countTestCases() > 0) { + currentSuite.addTest(subTestSuite); + } + } else { + classFiles.add(potentialDirectory); + } + } + Iterator fileIterator = classFiles.iterator(); + while (fileIterator.hasNext()) { + File file = (File) fileIterator.next(); + StringBuffer className = new StringBuffer(200); + className.append(currentPackageName); + className.append('.'); + String fileName = file.getName(); + className.append(fileName); + className.setLength(className.length() - 6); + currentSuite.addTest(new TestSuite(Class.forName(className.toString()), fileName.substring(0, fileName + .length() - 6))); + } + } + + private static class TestClassFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + if (name.endsWith("Test.class")) { + return true; + } + return new File(dir, name).isDirectory(); + } + } + + private static class FileComparator implements Comparator { + public int compare(File f1, File f2) { + return f1.getAbsolutePath().compareTo(f2.getAbsolutePath()); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/ApplicationTest.java b/installer/test/java/org/apache/commons/cli/ApplicationTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ApplicationTest.java @@ -0,0 +1,120 @@ +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + *

+ * This is a collection of tests that test real world + * applications command lines. + *

+ * + *

+ * The following are the applications that are tested: + *

    + *
  • Ant
  • + *
+ *

+ * + * @author John Keyes (john at integralsource.com) + */ +public class ApplicationTest extends TestCase { + + public static Test suite() { + return new TestSuite(ApplicationTest.class); + } + + public ApplicationTest(String name) + { + super(name); + } + + /** + * + */ + public void testLs() { + // create the command line parser + CommandLineParser parser = new PosixParser(); + Options options = new Options(); + options.addOption( "a", "all", false, "do not hide entries starting with ." ); + options.addOption( "A", "almost-all", false, "do not list implied . and .." ); + options.addOption( "b", "escape", false, "print octal escapes for nongraphic characters" ); + OptionBuilder.withLongOpt( "block-size" ); + OptionBuilder.withDescription( "use SIZE-byte blocks" ); + OptionBuilder.withValueSeparator( '=' ); + OptionBuilder.hasArg(); + options.addOption(OptionBuilder.create()); +// options.addOption( OptionBuilder.withLongOpt( "block-size" ) +// .withDescription( "use SIZE-byte blocks" ) +// .withValueSeparator( '=' ) +// .hasArg() +// .create() ); + options.addOption( "B", "ignore-backups", false, "do not list implied entried ending with ~"); + options.addOption( "c", false, "with -lt: sort by, and show, ctime (time of last modification of file status information) with -l:show ctime and sort by name otherwise: sort by ctime" ); + options.addOption( "C", false, "list entries by columns" ); + + String[] args = new String[]{ "--block-size=10" }; + + try { + CommandLine line = parser.parse( options, args ); + assertTrue( line.hasOption( "block-size" ) ); + assertEquals( line.getOptionValue( "block-size" ), "10" ); + } + catch( ParseException exp ) { + fail( "Unexpected exception:" + exp.getMessage() ); + } + } + + /** + * Ant test + */ + public void testAnt() { + // use the GNU parser + CommandLineParser parser = new GnuParser( ); + Options options = new Options(); + options.addOption( "help", false, "print this message" ); + options.addOption( "projecthelp", false, "print project help information" ); + options.addOption( "version", false, "print the version information and exit" ); + options.addOption( "quiet", false, "be extra quiet" ); + options.addOption( "verbose", false, "be extra verbose" ); + options.addOption( "debug", false, "print debug information" ); + options.addOption( "version", false, "produce logging information without adornments" ); + options.addOption( "logfile", true, "use given file for log" ); + options.addOption( "logger", true, "the class which is to perform the logging" ); + options.addOption( "listener", true, "add an instance of a class as a project listener" ); + options.addOption( "buildfile", true, "use given buildfile" ); + OptionBuilder.withDescription( "use value for given property" ); + OptionBuilder.hasArgs(); + OptionBuilder.withValueSeparator(); + options.addOption( OptionBuilder.create( 'D' ) ); + //, null, true, , false, true ); + options.addOption( "find", true, "search for buildfile towards the root of the filesystem and use it" ); + + String[] args = new String[]{ "-buildfile", "mybuild.xml", + "-Dproperty=value", "-Dproperty1=value1", + "-projecthelp" }; + + try { + CommandLine line = parser.parse( options, args ); + + // check multiple values + String[] opts = line.getOptionValues( "D" ); + assertEquals( "property", opts[0] ); + assertEquals( "value", opts[1] ); + assertEquals( "property1", opts[2] ); + assertEquals( "value1", opts[3] ); + + // check single value + assertEquals( line.getOptionValue( "buildfile"), "mybuild.xml" ); + + // check option + assertTrue( line.hasOption( "projecthelp") ); + } + catch( ParseException exp ) { + fail( "Unexpected exception:" + exp.getMessage() ); + } + + } + +} \ No newline at end of file diff --git a/installer/test/java/org/apache/commons/cli/BugsTest.java b/installer/test/java/org/apache/commons/cli/BugsTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/BugsTest.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: BugsTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class BugsTest extends TestCase +{ + + public static Test suite() { + return new TestSuite( BugsTest.class ); + } + + public BugsTest( String name ) + { + super( name ); + } + + public void setUp() + { + } + + public void tearDown() + { + } + + public void test11457() { + Options options = new Options(); + OptionBuilder.withLongOpt( "verbose" ); + options.addOption( OptionBuilder.create() ); + String[] args = new String[] { "--verbose" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertTrue( cmd.hasOption( "verbose" ) ); + } + catch( ParseException exp ) { + exp.printStackTrace(); + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test11458() + { + Options options = new Options(); + OptionBuilder.withValueSeparator( '=' ); + OptionBuilder.hasArgs(); + options.addOption(OptionBuilder.create( 'D' ) ); + OptionBuilder.withValueSeparator( ':' ); + OptionBuilder.hasArgs(); + options.addOption( OptionBuilder.create( 'p' ) ); + String[] args = new String[] { "-DJAVA_HOME=/opt/java" , + "-pfile1:file2:file3" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + + String[] values = cmd.getOptionValues( 'D' ); + + assertEquals( values[0], "JAVA_HOME" ); + assertEquals( values[1], "/opt/java" ); + + values = cmd.getOptionValues( 'p' ); + + assertEquals( values[0], "file1" ); + assertEquals( values[1], "file2" ); + assertEquals( values[2], "file3" ); + + java.util.Iterator iter = cmd.iterator(); + while( iter.hasNext() ) { + Option opt = (Option)iter.next(); + switch( opt.getId() ) { + case 'D': + assertEquals( opt.getValue( 0 ), "JAVA_HOME" ); + assertEquals( opt.getValue( 1 ), "/opt/java" ); + break; + case 'p': + assertEquals( opt.getValue( 0 ), "file1" ); + assertEquals( opt.getValue( 1 ), "file2" ); + assertEquals( opt.getValue( 2 ), "file3" ); + break; + default: + fail( "-D option not found" ); + } + } + } + catch( ParseException exp ) { + fail( "Unexpected Exception:\nMessage:" + exp.getMessage() + + "Type: " + exp.getClass().getName() ); + } + } + + public void test11680() + { + Options options = new Options(); + options.addOption("f", true, "foobar"); + options.addOption("m", true, "missing"); + String[] args = new String[] { "-f" , "foo" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + + try { + cmd.getOptionValue( "f", "default f"); + cmd.getOptionValue( "m", "default m"); + } + catch( NullPointerException exp ) { + fail( "NullPointer caught: " + exp.getMessage() ); + } + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test11456() + { + // Posix + Options options = new Options(); + OptionBuilder.hasOptionalArg(); + options.addOption( OptionBuilder.create( 'a' ) ); + OptionBuilder.hasArg(); + options.addOption( OptionBuilder.create( 'b' ) ); + String[] args = new String[] { "-a", "-bvalue" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertEquals( cmd.getOptionValue( 'b' ), "value" ); + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + + // GNU + options = new Options(); + OptionBuilder.hasOptionalArg(); + options.addOption( OptionBuilder.create( 'a' ) ); + OptionBuilder.hasArg(); + options.addOption( OptionBuilder.create( 'b' ) ); + args = new String[] { "-a", "-b", "value" }; + + parser = new GnuParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertEquals( cmd.getOptionValue( 'b' ), "value" ); + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + + } + + public void test12210() { + // create the main options object which will handle the first parameter + Options mainOptions = new Options(); + // There can be 2 main exclusive options: -exec|-rep + + // Therefore, place them in an option group + + String[] argv = new String[] { "-exec", "-exec_opt1", "-exec_opt2" }; + OptionGroup grp = new OptionGroup(); + + grp.addOption(new Option("exec",false,"description for this option")); + + grp.addOption(new Option("rep",false,"description for this option")); + + mainOptions.addOptionGroup(grp); + + // for the exec option, there are 2 options... + Options execOptions = new Options(); + execOptions.addOption("exec_opt1",false," desc"); + execOptions.addOption("exec_opt2",false," desc"); + + // similarly, for rep there are 2 options... + Options repOptions = new Options(); + repOptions.addOption("repopto",false,"desc"); + repOptions.addOption("repoptt",false,"desc"); + + // create the parser + GnuParser parser = new GnuParser(); + + // finally, parse the arguments: + + // first parse the main options to see what the user has specified + // We set stopAtNonOption to true so it does not touch the remaining + // options + try { + CommandLine cmd = parser.parse(mainOptions,argv,true); + // get the remaining options... + argv = cmd.getArgs(); + + if(cmd.hasOption("exec")){ + cmd = parser.parse(execOptions,argv,false); + // process the exec_op1 and exec_opt2... + assertTrue( cmd.hasOption("exec_opt1") ); + assertTrue( cmd.hasOption("exec_opt2") ); + } + else if(cmd.hasOption("rep")){ + cmd = parser.parse(repOptions,argv,false); + // process the rep_op1 and rep_opt2... + } + else { + fail( "exec option not found" ); + } + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + } + + public void test13425() { + Options options = new Options(); + OptionBuilder.withLongOpt( "old-password" ); + OptionBuilder.withDescription( "Use this option to specify the old password" ); + OptionBuilder.hasArg(); + Option oldpass = OptionBuilder.create( 'o' ); + OptionBuilder.withLongOpt( "new-password" ); + OptionBuilder.withDescription( "Use this option to specify the new password" ); + OptionBuilder.hasArg(); + Option newpass = OptionBuilder.create( 'n' ); + + String[] args = { + "-o", + "-n", + "newpassword" + }; + + options.addOption( oldpass ); + options.addOption( newpass ); + + Parser parser = new PosixParser(); + + CommandLine line = null; + try { + line = parser.parse( options, args ); + } + // catch the exception and leave the method + catch( Exception exp ) { + assertTrue( exp != null ); + return; + } + fail( "MissingArgumentException not caught." + line); + } + + public void test13666() { + Options options = new Options(); + OptionBuilder.withDescription( "dir" ); + OptionBuilder.hasArg(); + Option dir = OptionBuilder.create( 'd' ); + options.addOption( dir ); + try { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp( "dir", options ); + } + catch( Exception exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test13935() { + OptionGroup directions = new OptionGroup(); + + Option left = new Option( "l", "left", false, "go left" ); + Option right = new Option( "r", "right", false, "go right" ); + Option straight = new Option( "s", "straight", false, "go straight" ); + Option forward = new Option( "f", "forward", false, "go forward" ); + forward.setRequired( true ); + + directions.addOption( left ); + directions.addOption( right ); + directions.setRequired( true ); + + Options opts = new Options(); + opts.addOptionGroup( directions ); + opts.addOption( straight ); + + CommandLineParser parser = new PosixParser(); + boolean exception = false; + + CommandLine line = null; + String[] args = new String[] { }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + exception = true; + } + + if( !exception ) { + fail( "Expected exception not caught."); + } + + exception = false; + + args = new String[] { "-s" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + exception = true; + } + + if( !exception ) { + fail( "Expected exception not caught."); + } + + exception = false; + + args = new String[] { "-s", "-l" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + + opts.addOption( forward ); + args = new String[] { "-s", "-l", "-f" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + if(line != null) { + line = null; + } + } +} diff --git a/installer/test/java/org/apache/commons/cli/BuildTest.java b/installer/test/java/org/apache/commons/cli/BuildTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/BuildTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: BuildTest.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class BuildTest extends TestCase +{ + + public static Test suite() { + return new TestSuite(BuildTest.class); + } + + public BuildTest(String name) + { + super(name); + } + + public void setUp() + { + + } + + public void tearDown() + { + + } + + public void testSimple() + { + Options opts = new Options(); + + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "toggle -b"); + } + + public void testDuplicateSimple() + { + Options opts = new Options(); + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("a", + true, + "toggle -a*"); + + assertEquals( "last one in wins", "toggle -a*", opts.getOption("a").getDescription() ); + } + + public void testLong() + { + Options opts = new Options(); + + opts.addOption("a", + "--a", + false, + "toggle -a"); + + opts.addOption("b", + "--b", + true, + "set -b"); + + } + + public void testDuplicateLong() + { + Options opts = new Options(); + opts.addOption("a", + "--a", + false, + "toggle -a"); + + opts.addOption("a", + "--a", + false, + "toggle -a*"); + assertEquals( "last one in wins", "toggle -a*", opts.getOption("a").getDescription() ); + } +} diff --git a/installer/test/java/org/apache/commons/cli/GnuParseTest.java b/installer/test/java/org/apache/commons/cli/GnuParseTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/GnuParseTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: GnuParseTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class GnuParseTest extends TestCase +{ + private Options _options = null; + private CommandLineParser _parser = null; + + public static Test suite() { + return new TestSuite( GnuParseTest.class ); + } + + public GnuParseTest( String name ) + { + super( name ); + } + + public void setUp() + { + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption("b", + "bfile", + true, + "set the value of [b]") + .addOption("c", + "copt", + false, + "turn [c] on or off"); + + _parser = new GnuParser( ); + } + + public void tearDown() + { + + } + + public void testSimpleShort() + { + String[] args = new String[] { "-a", + "-b", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSimpleLong() + { + String[] args = new String[] { "--enable-a", + "--bfile", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testExtraOption() + { + String[] args = new String[] { "-a", "-d", "-b", "toast", + "foo", "bar" }; + + boolean caught = false; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 3); + } + catch (UnrecognizedOptionException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + assertTrue( "Confirm UnrecognizedOptionException caught", caught ); + } + + public void testMissingArg() + { + + String[] args = new String[] { "-b" }; + + boolean caught = false; + + CommandLine cl = null; + try + { + cl = _parser.parse(_options, args); + } + catch (MissingArgumentException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + + assertTrue( "Confirm MissingArgumentException caught " + cl, caught ); + } + + public void testStop() + { + String[] args = new String[] { "-c", + "foober", + "-b", + "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultiple() + { + String[] args = new String[] { "-c", + "foobar", + "-b", + "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultipleWithLong() + { + String[] args = new String[] { "--copt", + "foobar", + "--bfile", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options,args, + true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testDoubleDash() + { + String[] args = new String[] { "--copt", + "--", + "-b", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm -b is not set", ! cl.hasOption("b") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleDash() + { + String[] args = new String[] { "--copt", + "-b", "-", + "-a", + "-" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("-") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("-") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + + } +} diff --git a/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java b/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: HelpFormatterExamples.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ +package org.apache.commons.cli; + +/** + * A sample program shpwing the use of Options and the HelpFormatter class + * + * @author Slawek Zachcial + **/ +public class HelpFormatterExamples +{ + // --------------------------------------------------------------- Constants + + // ------------------------------------------------------------------ Static + + public static void main( String[] args ) + { + System.out.println("\n#\n# 'man' example\n#"); + manExample(); +/* + System.out.println("\n#\n# 'bzip2' example\n#"); + bzip2Example(); + System.out.println("\n#\n# 'ls' example\n#"); + lsExample(); +*/ + } + + static void manExample() + { + String cmdLine = + "man [-c|-f|-k|-w|-tZT device] [-adlhu7V] [-Mpath] [-Ppager] [-Slist] " + + "[-msystem] [-pstring] [-Llocale] [-eextension] [section] page ..."; + Options opts = + new Options(). + addOption("a", "all", false, "find all matching manual pages."). + addOption("d", "debug", false, "emit debugging messages."). + addOption("e", "extension", false, "limit search to extension type 'extension'."). + addOption("f", "whatis", false, "equivalent to whatis."). + addOption("k", "apropos", false, "equivalent to apropos."). + addOption("w", "location", false, "print physical location of man page(s)."). + addOption("l", "local-file", false, "interpret 'page' argument(s) as local filename(s)"). + addOption("u", "update", false, "force a cache consistency check."). + //FIXME - should generate -r,--prompt string + addOption("r", "prompt", true, "provide 'less' pager with prompt."). + addOption("c", "catman", false, "used by catman to reformat out of date cat pages."). + addOption("7", "ascii", false, "display ASCII translation or certain latin1 chars."). + addOption("t", "troff", false, "use troff format pages."). + //FIXME - should generate -T,--troff-device device + addOption("T", "troff-device", true, "use groff with selected device."). + addOption("Z", "ditroff", false, "use groff with selected device."). + addOption("D", "default", false, "reset all options to their default values."). + //FIXME - should generate -M,--manpath path + addOption("M", "manpath", true, "set search path for manual pages to 'path'."). + //FIXME - should generate -P,--pager pager + addOption("P", "pager", true, "use program 'pager' to display output."). + //FIXME - should generate -S,--sections list + addOption("S", "sections", true, "use colon separated section list."). + //FIXME - should generate -m,--systems system + addOption("m", "systems", true, "search for man pages from other unix system(s)."). + //FIXME - should generate -L,--locale locale + addOption("L", "locale", true, "defaine the locale for this particular man search."). + //FIXME - should generate -p,--preprocessor string + addOption("p", "preprocessor", true, "string indicates which preprocessor to run.\n" + + " e - [n]eqn p - pic t - tbl\n" + + " g - grap r - refer v - vgrind"). + addOption("V", "version", false, "show version."). + addOption("h", "help", false, "show this usage message."); + + HelpFormatter hf = new HelpFormatter(); + //hf.printHelp(cmdLine, opts); + hf.printHelp(60, cmdLine, null, opts, null); + } + + static void bzip2Example() + { + System.out.println( "Coming soon" ); + } + + static void lsExample() + { + System.out.println( "Coming soon" ); + } + + + // -------------------------------------------------------------- Attributes + + // ------------------------------------------------------------ Constructors + + // ------------------------------------------------------------------ Public + + // --------------------------------------------------------------- Protected + + // ------------------------------------------------------- Package protected + + // ----------------------------------------------------------------- Private + + // ----------------------------------------------------------- Inner classes + +} diff --git a/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java b/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java @@ -0,0 +1,82 @@ +package org.apache.commons.cli; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +import junit.framework.TestCase; + +public class HelpFormatterTest extends TestCase { + + private Options _options; + + protected void setUp() { + _options = new Options(); + Option aOption = new Option("a", "Aa", false, "option A"); + Option bOption = new Option("b", "Bb", false, "option B"); + OptionGroup group1 = new OptionGroup(); + group1.addOption(aOption); + group1.addOption(bOption); + _options.addOptionGroup(group1); + } + + /** + * the setUp above used to print [-a | -b] [-a] [-b] + */ + public void testOptionGroupDuplication() { + String help = unifyNewLines(getFormattedHelp()); + String expectedHelp = unifyNewLines(new String("usage: syntax [-a | -b]\n-a,--Aa option A\n-b,--Bb option B\n")); + assertEquals("expected usage to be '" + expectedHelp + "' instead of '" + help + "'", + expectedHelp, + help); + } + + /** + * Options following an option group used to be non blank separated: [-b | -a][-o] instead of + * [-b | -a] [-o] + */ + public void testOptionGroupSubsequentOptions() { + _options.addOption(new Option("o", "Option O")); + String help = getFormattedHelp(); + assertTrue(help.indexOf("][") < 0); + assertTrue(help.indexOf("[-a | -b] [-o]") >= 0); + } + + // + // private methods + // + private String getFormattedHelp() { + HelpFormatter formatter = new HelpFormatter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter pw = new PrintWriter(baos); + formatter.printHelp(pw, 60, "syntax", null, _options, 0, 1, null, true); + pw.flush(); + String usage = baos.toString(); + return usage; + } + + /** + * replace the windows specific \r\n line endings with java like \n line endings + * + * @param in + * The string to be transformed + * @return The string with unified line endings + */ + private String unifyNewLines(String in) { + char[] inChars = in.toCharArray(); + StringBuilder b = new StringBuilder(inChars.length); + for (int i = 0; i < inChars.length; i++) { + char current = inChars[i]; + if (current == '\r') { + if (i < inChars.length) { + char next = inChars[i + 1]; + if (next == '\n') { + i++; + current = next; + } + } + } + b.append(current); + } + return b.toString(); + } +} diff --git a/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java b/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java @@ -0,0 +1,160 @@ +package org.apache.commons.cli; + +import java.math.BigDecimal; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import junit.textui.TestRunner; + +public class OptionBuilderTest extends TestCase { + + public OptionBuilderTest( String name ) { + super( name ); + } + + public static Test suite() { + return new TestSuite( OptionBuilderTest.class ); + } + + public static void main( String args[] ) { + TestRunner.run( suite() ); + } + + public void testCompleteOption( ) { + OptionBuilder.withLongOpt( "simple option"); + OptionBuilder.hasArg( ); + OptionBuilder.isRequired( ); + OptionBuilder.hasArgs( ); + OptionBuilder.withType( new BigDecimal( "10" ) ); + OptionBuilder.withDescription( "this is a simple option" ); + Option simple = OptionBuilder.create( 's' ); + + assertEquals( "s", simple.getOpt() ); + assertEquals( "simple option", simple.getLongOpt() ); + assertEquals( "this is a simple option", simple.getDescription() ); + assertEquals( simple.getType().getClass(), BigDecimal.class ); + assertTrue( simple.hasArg() ); + assertTrue( simple.isRequired() ); + assertTrue( simple.hasArgs() ); + } + + public void testTwoCompleteOptions( ) { + OptionBuilder.withLongOpt( "simple option"); + OptionBuilder.hasArg( ); + OptionBuilder.isRequired( ); + OptionBuilder.hasArgs( ); + OptionBuilder.withType( new BigDecimal( "10" ) ); + OptionBuilder.withDescription( "this is a simple option" ); + Option simple = OptionBuilder.create( 's' ); + + assertEquals( "s", simple.getOpt() ); + assertEquals( "simple option", simple.getLongOpt() ); + assertEquals( "this is a simple option", simple.getDescription() ); + assertEquals( simple.getType().getClass(), BigDecimal.class ); + assertTrue( simple.hasArg() ); + assertTrue( simple.isRequired() ); + assertTrue( simple.hasArgs() ); + + OptionBuilder.withLongOpt( "dimple option"); + OptionBuilder.hasArg( ); + OptionBuilder.withDescription( "this is a dimple option" ); + simple = OptionBuilder.create( 'd' ); + + assertEquals( "d", simple.getOpt() ); + assertEquals( "dimple option", simple.getLongOpt() ); + assertEquals( "this is a dimple option", simple.getDescription() ); + assertNull( simple.getType() ); + assertTrue( simple.hasArg() ); + assertTrue( !simple.isRequired() ); + assertTrue( !simple.hasArgs() ); + } + + public void testBaseOptionCharOpt() { + OptionBuilder.withDescription( "option description"); + Option base = OptionBuilder.create( 'o' ); + + assertEquals( "o", base.getOpt() ); + assertEquals( "option description", base.getDescription() ); + assertTrue( !base.hasArg() ); + } + + public void testBaseOptionStringOpt() { + OptionBuilder.withDescription( "option description"); + Option base = OptionBuilder.create( "o" ); + + assertEquals( "o", base.getOpt() ); + assertEquals( "option description", base.getDescription() ); + assertTrue( !base.hasArg() ); + } + + public void testSpecialOptChars() { + + // '?' + try { + OptionBuilder.withDescription( "help options" ); + Option opt = OptionBuilder.create( '?' ); + assertEquals( "?", opt.getOpt() ); + } + catch( IllegalArgumentException arg ) { + fail( "IllegalArgumentException caught" ); + } + + // '@' + try { + OptionBuilder.withDescription( "read from stdin" ); + Option opt = OptionBuilder.create( '@' ); + assertEquals( "@", opt.getOpt() ); + } + catch( IllegalArgumentException arg ) { + fail( "IllegalArgumentException caught" ); + } + } + + public void testOptionArgNumbers() { + OptionBuilder.withDescription( "option description" ); + OptionBuilder.hasArgs( 2 ); + Option opt = OptionBuilder.create( 'o' ); + assertEquals( 2, opt.getArgs() ); + } + + public void testIllegalOptions() { + // bad single character option + try { + OptionBuilder.withDescription( "option description" ); + OptionBuilder.create( '"' ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // bad character in option string + try { + OptionBuilder.create( "opt`" ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // null option + try { + OptionBuilder.create( null ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // valid option + try { + OptionBuilder.create( "opt" ); + // success + } + catch( IllegalArgumentException exp ) { + fail( "IllegalArgumentException caught" ); + } + } +} \ No newline at end of file diff --git a/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java b/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java @@ -0,0 +1,43 @@ +package org.apache.commons.cli; + +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.TestCase; + +public class OptionGroupSortTest extends TestCase { + + private OptionGroup _optionGroup; + + protected void setUp() { + _optionGroup = new OptionGroup(); + _optionGroup.addOption(new Option("f", "first", false, "first")); + _optionGroup.addOption(new Option("s", "second", false, "second")); + _optionGroup.addOption(new Option("t", "third", false, "third")); + } + + public void testSortNames() { + Collection names = _optionGroup.getNames(); + Iterator namesIterator = names.iterator(); + assertTrue(namesIterator.hasNext()); + assertEquals("-f", (String) namesIterator.next()); + assertTrue(namesIterator.hasNext()); + assertEquals("-s", (String) namesIterator.next()); + assertTrue(namesIterator.hasNext()); + assertEquals("-t", (String) namesIterator.next()); + assertFalse(namesIterator.hasNext()); + } + + public void testSortOptions() { + Collection options = _optionGroup.getOptions(); + Iterator optionIterator = options.iterator(); + assertTrue(optionIterator.hasNext()); + assertEquals("first", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("second", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("third", ((Option) optionIterator.next()).getLongOpt()); + assertFalse(optionIterator.hasNext()); + } + +} diff --git a/installer/test/java/org/apache/commons/cli/OptionGroupTest.java b/installer/test/java/org/apache/commons/cli/OptionGroupTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionGroupTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: OptionGroupTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * @author John Keyes (john at integralsource.com) + * @version $Revision: 3134 $ + */ +public class OptionGroupTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser parser = new PosixParser(); + + + public static Test suite() + { + return new TestSuite ( OptionGroupTest.class ); + } + + public OptionGroupTest( String name ) + { + super( name ); + } + + public void setUp() + { + Option file = new Option( "f", "file", false, "file to process" ); + Option dir = new Option( "d", "directory", false, "directory to process" ); + OptionGroup group = new OptionGroup(); + group.addOption( file ); + group.addOption( dir ); + _options = new Options().addOptionGroup( group ); + + Option section = new Option( "s", "section", false, "section to process" ); + Option chapter = new Option( "c", "chapter", false, "chapter to process" ); + OptionGroup group2 = new OptionGroup(); + group2.addOption( section ); + group2.addOption( chapter ); + + _options.addOptionGroup( group2 ); + _options.addOption( "r", "revision", false, "revision number" ); + } + + public void tearDown() + { + } + + public void testSingleOptionFromGroup() + { + String[] args = new String[] { "-f" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleOption() + { + String[] args = new String[] { "-r" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is NOT set", !cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoValidOptions() + { + String[] args = new String[] { "-r", "-f" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleLongOption() + { + String[] args = new String[] { "--file" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoValidLongOptions() + { + String[] args = new String[] { "--revision", "--file" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testNoOptionsExtraArgs() + { + String[] args = new String[] { "arg1", "arg2" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is NOT set", !cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm TWO extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoOptionsFromGroup() + { + String[] args = new String[] { "-f", "-d" }; + + CommandLine cl = null; + try + { + cl = parser.parse( _options, args); + fail( "two arguments from group not allowed" ); + } + catch (ParseException e) + { + if( !( e instanceof AlreadySelectedException ) ) + { + fail( "incorrect exception caught:" + e.getMessage() + " in " + cl ); + } + } + } + + public void testTwoLongOptionsFromGroup() + { + String[] args = new String[] { "--file", "--directory" }; + + CommandLine cl = null; + try + { + cl = parser.parse( _options, args); + fail( "two arguments from group not allowed" ); + } + catch (ParseException e) + { + if( !( e instanceof AlreadySelectedException ) ) + { + fail( "incorrect exception caught:" + e.getMessage() + " in " + cl ); + } + } + } + + public void testTwoOptionsFromDifferentGroup() + { + String[] args = new String[] { "-f", "-s" }; + + try + { + CommandLine cl = parser.parse( _options, args); + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is set", cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm NO extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + +} diff --git a/installer/test/java/org/apache/commons/cli/OptionsTest.java b/installer/test/java/org/apache/commons/cli/OptionsTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionsTest.java @@ -0,0 +1,28 @@ +package org.apache.commons.cli; + +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.TestCase; + +public class OptionsTest extends TestCase { + + private Options _options; + + public void testSortAsAdded() { + _options = new Options(); + _options.setSortAsAdded(true); + _options.addOption("f", "first", false, "first"); + _options.addOption("s", "second", false, "second"); + _options.addOption("t", "third", false, "third"); + Collection optionCollection = _options.getOptions(); + Iterator optionIterator = optionCollection.iterator(); + assertTrue(optionIterator.hasNext()); + assertEquals("first", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("second", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("third", ((Option) optionIterator.next()).getLongOpt()); + assertFalse(optionIterator.hasNext()); + } +} diff --git a/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java b/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ParseRequiredTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * @author John Keyes (john at integralsource.com) + * @version $Revision: 3134 $ + */ +public class ParseRequiredTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser parser = new PosixParser(); + + public static Test suite() { + return new TestSuite(ParseRequiredTest.class); + } + + public ParseRequiredTest(String name) + { + super(name); + } + + public void setUp() + { + OptionBuilder.withLongOpt( "bfile" ); + OptionBuilder.hasArg(); + OptionBuilder.isRequired(); + OptionBuilder.withDescription( "set the value of [b]" ); + Option opt2 = OptionBuilder.create( 'b' ); + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption( opt2 ); + } + + public void tearDown() + { + + } + + public void testWithRequiredOption() + { + String[] args = new String[] { "-b", "file" }; + + try + { + CommandLine cl = parser.parse(_options,args); + + assertTrue( "Confirm -a is NOT set", !cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("file") ); + assertTrue( "Confirm NO of extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testOptionAndRequiredOption() + { + String[] args = new String[] { "-a", "-b", "file" }; + + try + { + CommandLine cl = parser.parse(_options,args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("file") ); + assertTrue( "Confirm NO of extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMissingRequiredOption() + { + String[] args = new String[] { "-a" }; + + CommandLine cl = null; + try + { + cl = parser.parse(_options,args); + fail( "exception should have been thrown" ); + } + catch (ParseException e) + { + if( !( e instanceof MissingOptionException ) ) + { + fail( "expected to catch MissingOptionException in " + cl ); + } + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/ParseTest.java b/installer/test/java/org/apache/commons/cli/ParseTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ParseTest.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ParseTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ParseTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser _parser = null; + + public static Test suite() { + return new TestSuite(ParseTest.class); + } + + public ParseTest(String name) + { + super(name); + } + + public void setUp() + { + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption("b", + "bfile", + true, + "set the value of [b]") + .addOption("c", + "copt", + false, + "turn [c] on or off"); + + _parser = new PosixParser(); + } + + public void tearDown() + { + + } + + public void testSimpleShort() + { + String[] args = new String[] { "-a", + "-b", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSimpleLong() + { + String[] args = new String[] { "--enable-a", + "--bfile", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testComplexShort() + { + String[] args = new String[] { "-acbtoast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testExtraOption() + { + String[] args = new String[] { "-adbtoast", + "foo", "bar" }; + + boolean caught = false; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 3); + } + catch (UnrecognizedOptionException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + assertTrue( "Confirm UnrecognizedOptionException caught", caught ); + } + + public void testMissingArg() + { + + String[] args = new String[] { "-acb" }; + + boolean caught = false; + + CommandLine cl = null; + try + { + cl = _parser.parse(_options, args); + } + catch (MissingArgumentException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + + assertTrue( "Confirm MissingArgumentException caught " + cl, caught ); + } + + public void testStop() + { + String[] args = new String[] { "-c", + "foober", + "-btoast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultiple() + { + String[] args = new String[] { "-c", + "foobar", + "-btoast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultipleWithLong() + { + String[] args = new String[] { "--copt", + "foobar", + "--bfile", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options,args, + true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testDoubleDash() + { + String[] args = new String[] { "--copt", + "--", + "-b", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm -b is not set", ! cl.hasOption("b") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleDash() + { + String[] args = new String[] { "--copt", + "-b", "-", + "-a", + "-" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("-") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("-") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + + } +} diff --git a/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java b/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: PatternOptionBuilderTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ +package org.apache.commons.cli; + +import java.math.BigDecimal; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test case for the PatternOptionBuilder class + * + * @author Henri Yandell + **/ +public class PatternOptionBuilderTest +extends TestCase +{ + public static void main( String[] args ) + { + String[] testName = { PatternOptionBuilderTest.class.getName() }; + junit.textui.TestRunner.main(testName); + } + + public static TestSuite suite() + { + return new TestSuite(PatternOptionBuilderTest.class); + } + + public PatternOptionBuilderTest( String s ) + { + super( s ); + } + + public void testSimplePattern() + { + try { + Options options = PatternOptionBuilder.parsePattern("a:b at cde>f+n%t/"); + String[] args = new String[] { "-c", "-a", "foo", "-b", "java.util.Vector", "-e", "build.xml", "-f", "java.util.Calendar", "-n", "4.5", "-t", "http://jakarta.apache.org/" }; + + CommandLineParser parser = new PosixParser(); + CommandLine line = parser.parse(options,args); + + // tests the char methods of CommandLine that delegate to + // the String methods + assertEquals("flag a", "foo", line.getOptionValue("a")); + assertEquals("flag a", "foo", line.getOptionValue('a')); + assertEquals("string flag a", "foo", line.getOptionObject("a")); + assertEquals("string flag a", "foo", line.getOptionObject('a')); + assertEquals("object flag b", new java.util.Vector(), line.getOptionObject("b")); + assertEquals("object flag b", new java.util.Vector(), line.getOptionObject('b')); + assertEquals("boolean true flag c", true, line.hasOption("c")); + assertEquals("boolean true flag c", true, line.hasOption('c')); + assertEquals("boolean false flag d", false, line.hasOption("d")); + assertEquals("boolean false flag d", false, line.hasOption('d')); + assertEquals("file flag e", new java.io.File("build.xml"), line.getOptionObject("e")); + assertEquals("file flag e", new java.io.File("build.xml"), line.getOptionObject('e')); + assertEquals("class flag f", java.util.Calendar.class, line.getOptionObject("f")); + assertEquals("class flag f", java.util.Calendar.class, line.getOptionObject('f')); + assertEquals("number flag n", new BigDecimal("4.5"), line.getOptionObject("n")); + assertEquals("number flag n", new BigDecimal("4.5"), line.getOptionObject('n')); + assertEquals("url flag t", new java.net.URL("http://jakarta.apache.org/"), line.getOptionObject("t")); + assertEquals("url flag t", new java.net.URL("http://jakarta.apache.org/"), line.getOptionObject('t')); + /// DATES NOT SUPPORTED YET. + // assertEquals("number flag t", new java.util.Date(1023400137276L), line.getOptionObject('z')); + // input is: "Thu Jun 06 17:48:57 EDT 2002" + } + catch( ParseException exp ) { + fail( exp.getMessage() ); + } + catch( java.net.MalformedURLException exp ) { + fail( exp.getMessage() ); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/PosixParserTest.java b/installer/test/java/org/apache/commons/cli/PosixParserTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/PosixParserTest.java @@ -0,0 +1,138 @@ +package org.apache.commons.cli; + +import junit.framework.TestCase; + +public class PosixParserTest extends TestCase { + + private static final String TEST_SHORT = "t"; + private static final String TEST_LONG = "test"; + private static final String TEST_DESC = "test option"; + + private static final String TEST_SHORT_OPTION = "-t"; + private static final String TEST_LONG_OPTION = "--test"; + + private static final String ARGUMENT = "argument"; + + private Options _options; + private Parser _parser; + + protected void setUp() { + _parser = new PosixParser(); + + _options = new Options(); + Option testOption = new Option(TEST_SHORT, TEST_LONG, false, TEST_DESC); + _options.addOption(testOption); + } + + /** + * test that an unknown single option and a double hyphen option (with or without argument) are treated the same + */ + public void testFlattenStop() { + boolean stopAtNonOption = true; // means unallowed tokens should not be added + String[] args; + String[] expectedFlattened; + + // unknown single dash option + args = new String[] { "-u" }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "-u", TEST_SHORT_OPTION }; + expectedFlattened = new String[] { TEST_SHORT_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option + args = new String[] { "--unknown" }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", TEST_LONG_OPTION }; + expectedFlattened = new String[] { TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after = + args = new String[] { "--unknown=" + ARGUMENT }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown="+ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after ' ' + args = new String[] { "--unknown", ARGUMENT }; + expectedFlattened = new String[] { ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + /** + * test that an unknown single option and a double hyphen option (with or without argument) are treated the same + */ + public void testFlattenNoStop() { + boolean stopAtNonOption = false; // means every token should be added + String[] args; + String[] expectedFlattened; + + // unknown single dash option + args = new String[] { "-u" }; + expectedFlattened = new String[] { "-u" }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "-u", TEST_SHORT_OPTION }; + expectedFlattened = new String[] { "-u", TEST_SHORT_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option + args = new String[] { "--unknown" }; + expectedFlattened = new String[] { "--unknown" }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after = + args = new String[] { "--unknown=" + ARGUMENT }; + expectedFlattened = new String[] { "--unknown", ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown="+ ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after ' ' + args = new String[] { "--unknown", ARGUMENT }; + expectedFlattened = new String[] { "--unknown", ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + /** + * test that a misspelled long option (-test instead of --test) is not interpreted as -t est + */ + public void testMisspelledLongOption() { + boolean stopAtNonOption = false; // means every token should be added + String[] args; + String[] expectedFlattened; + + // unknown single dash long option + String singleDashLongOption = "-" + TEST_LONG; + args = new String[] { singleDashLongOption }; + expectedFlattened = new String[] { singleDashLongOption }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + // + // private stuff + // + + /** + * Assert that the content of the specified object arrays is equal + */ + private void assertEquals(Object[] correct, Object[] tested) { + assertEquals("different array lengths:", correct.length, tested.length); + for (int i = 0; i < correct.length; i++) { + assertEquals("position " + i + " of array differs", correct[i], tested[i]); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java b/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: TestHelpFormatter.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ +package org.apache.commons.cli; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test case for the HelpFormatter class + * + * @author Slawek Zachcial + * @author John Keyes ( john at integralsource.com ) + **/ +public class TestHelpFormatter extends TestCase +{ + public static void main( String[] args ) + { + String[] testName = { TestHelpFormatter.class.getName() }; + junit.textui.TestRunner.main(testName); + } + + public static TestSuite suite() + { + return new TestSuite(TestHelpFormatter.class); + } + + public TestHelpFormatter( String s ) + { + super( s ); + } + + public void testFindWrapPos() + throws Exception + { + HelpFormatter hf = new HelpFormatter(); + + String text = "This is a test."; + //text width should be max 8; the wrap postition is 7 + assertEquals("wrap position", 7, hf.findWrapPos(text, 8, 0)); + //starting from 8 must give -1 - the wrap pos is after end + assertEquals("wrap position 2", -1, hf.findWrapPos(text, 8, 8)); + //if there is no a good position before width to make a wrapping look for the next one + text = "aaaa aa"; + assertEquals("wrap position 3", 4, hf.findWrapPos(text, 3, 0)); + } + + public void testPrintWrapped() + throws Exception + { + StringBuffer sb = new StringBuffer(); + HelpFormatter hf = new HelpFormatter(); + + String text = "This is a test."; + String expected; + + expected = "This is a" + hf.defaultNewLine + "test."; + hf.renderWrappedText(sb, 12, 0, text); + assertEquals("single line text", expected, sb.toString()); + + sb.setLength(0); + expected = "This is a" + hf.defaultNewLine + " test."; + hf.renderWrappedText(sb, 12, 4, text); + assertEquals("single line padded text", expected, sb.toString()); + + text = + "aaaa aaaa aaaa" + hf.defaultNewLine + + "aaaaaa" + hf.defaultNewLine + + "aaaaa"; + + expected = text; + sb.setLength(0); + hf.renderWrappedText(sb, 16, 0, text); + assertEquals("multi line text", expected, sb.toString()); + + expected = + "aaaa aaaa aaaa" + hf.defaultNewLine + + " aaaaaa" + hf.defaultNewLine + + " aaaaa"; + sb.setLength(0); + hf.renderWrappedText(sb, 16, 4, text); + assertEquals("multi-line padded text", expected, sb.toString()); + } + + public void testPrintOptions() + throws Exception + { + StringBuffer sb = new StringBuffer(); + HelpFormatter hf = new HelpFormatter(); + final int leftPad = 1; + final int descPad = 3; + final String lpad = hf.createPadding(leftPad); + final String dpad = hf.createPadding(descPad); + Options options = null; + String expected = null; + + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa"); + expected = lpad + "-a" + dpad + "aaaa aaaa aaaa aaaa aaaa"; + hf.renderOptions(sb, 60, options, leftPad, descPad); + assertEquals("simple non-wrapped option", expected, sb.toString()); + + int nextLineTabStop = leftPad+descPad+"-a".length(); + expected = + lpad + "-a" + dpad + "aaaa aaaa aaaa" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "aaaa aaaa"; + sb.setLength(0); + hf.renderOptions(sb, nextLineTabStop+17, options, leftPad, descPad); + assertEquals("simple wrapped option", expected, sb.toString()); + + + options = new Options().addOption("a", "aaa", false, "dddd dddd dddd dddd"); + expected = lpad + "-a,--aaa" + dpad + "dddd dddd dddd dddd"; + sb.setLength(0); + hf.renderOptions(sb, 60, options, leftPad, descPad); + assertEquals("long non-wrapped option", expected, sb.toString()); + + nextLineTabStop = leftPad+descPad+"-a,--aaa".length(); + expected = + lpad + "-a,--aaa" + dpad + "dddd dddd" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "dddd dddd"; + sb.setLength(0); + hf.renderOptions(sb, 25, options, leftPad, descPad); + assertEquals("long wrapped option", expected, sb.toString()); + + options = new Options(). + addOption("a", "aaa", false, "dddd dddd dddd dddd"). + addOption("b", false, "feeee eeee eeee eeee"); + expected = + lpad + "-a,--aaa" + dpad + "dddd dddd" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "dddd dddd" + hf.defaultNewLine + + lpad + "-b " + dpad + "feeee eeee" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "eeee eeee"; + sb.setLength(0); + hf.renderOptions(sb, 25, options, leftPad, descPad); + assertEquals("multiple wrapped options", expected, sb.toString()); + } + + public void testAutomaticUsage() + throws Exception + { + HelpFormatter hf = new HelpFormatter(); + Options options = null; + String expected = "usage: app [-a]"; + ByteArrayOutputStream out = new ByteArrayOutputStream( ); + PrintWriter pw = new PrintWriter( out ); + + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa"); + hf.printUsage( pw, 60, "app", options ); + pw.flush(); + assertEquals("simple auto usage", expected, out.toString().trim()); + out.reset(); + + expected = "usage: app [-b] [-a]"; + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa") + .addOption("b", false, "bbb" ); + hf.printUsage( pw, 60, "app", options ); + pw.flush(); + assertEquals("simple auto usage", expected, out.toString().trim()); + out.reset(); + } +} diff --git a/installer/test/java/org/apache/commons/cli/ValueTest.java b/installer/test/java/org/apache/commons/cli/ValueTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ValueTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ValueTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ValueTest extends TestCase +{ + + public static Test suite() { + return new TestSuite(ValueTest.class); + } + + private CommandLine _cl = null; + private Options opts = new Options(); + + public ValueTest(String name) + { + super(name); + } + + public void setUp() + { + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "set -b"); + + opts.addOption("c", + "c", + false, + "toggle -c"); + + opts.addOption("d", + "d", + true, + "set -d"); + + OptionBuilder.hasOptionalArg(); + opts.addOption( OptionBuilder.create( 'e') ); + + OptionBuilder.hasOptionalArg(); + OptionBuilder.withLongOpt( "fish" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs(); + OptionBuilder.withLongOpt( "gravy" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs( 2 ); + OptionBuilder.withLongOpt( "hide" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs( 2 ); + opts.addOption( OptionBuilder.create( 'i' ) ); + + OptionBuilder.hasOptionalArgs( ); + opts.addOption( OptionBuilder.create( 'j' ) ); + + String[] args = new String[] { "-a", + "-b", "foo", + "--c", + "--d", "bar" + }; + + try + { + CommandLineParser parser = new PosixParser(); + _cl = parser.parse(opts,args); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void tearDown() + { + + } + + public void testShortNoArg() + { + assertTrue( _cl.hasOption("a") ); + assertNull( _cl.getOptionValue("a") ); + } + + public void testShortWithArg() + { + assertTrue( _cl.hasOption("b") ); + assertNotNull( _cl.getOptionValue("b") ); + assertEquals( _cl.getOptionValue("b"), "foo"); + } + + public void testLongNoArg() + { + assertTrue( _cl.hasOption("c") ); + assertNull( _cl.getOptionValue("c") ); + } + + public void testLongWithArg() + { + assertTrue( _cl.hasOption("d") ); + assertNotNull( _cl.getOptionValue("d") ); + assertEquals( _cl.getOptionValue("d"), "bar"); + } + + public void testShortOptionalArgNoValue() + { + String[] args = new String[] { "-e" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("e") ); + assertNull( cmd.getOptionValue("e") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalArgValue() + { + String[] args = new String[] { "-e", "everything" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("e") ); + assertEquals( "everything", cmd.getOptionValue("e") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalNoValue() + { + String[] args = new String[] { "--fish" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("fish") ); + assertNull( cmd.getOptionValue("fish") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalArgValue() + { + String[] args = new String[] { "--fish", "face" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("fish") ); + assertEquals( "face", cmd.getOptionValue("fish") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalArgValues() + { + String[] args = new String[] { "-j", "ink", "idea" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("j") ); + assertEquals( "ink", cmd.getOptionValue("j") ); + assertEquals( "ink", cmd.getOptionValues("j")[0] ); + assertEquals( "idea", cmd.getOptionValues("j")[1] ); + assertEquals( cmd.getArgs().length, 0 ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalArgValues() + { + String[] args = new String[] { "--gravy", "gold", "garden" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("gravy") ); + assertEquals( "gold", cmd.getOptionValue("gravy") ); + assertEquals( "gold", cmd.getOptionValues("gravy")[0] ); + assertEquals( "garden", cmd.getOptionValues("gravy")[1] ); + assertEquals( cmd.getArgs().length, 0 ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalNArgValues() + { + String[] args = new String[] { "-i", "ink", "idea", "isotope", "ice" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("i") ); + assertEquals( "ink", cmd.getOptionValue("i") ); + assertEquals( "ink", cmd.getOptionValues("i")[0] ); + assertEquals( "idea", cmd.getOptionValues("i")[1] ); + assertEquals( cmd.getArgs().length, 2 ); + assertEquals( "isotope", cmd.getArgs()[0] ); + assertEquals( "ice", cmd.getArgs()[1] ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalNArgValues() + { + String[] args = new String[] { "--hide", "house", "hair", "head" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("hide") ); + assertEquals( "house", cmd.getOptionValue("hide") ); + assertEquals( "house", cmd.getOptionValues("hide")[0] ); + assertEquals( "hair", cmd.getOptionValues("hide")[1] ); + assertEquals( cmd.getArgs().length, 1 ); + assertEquals( "head", cmd.getArgs()[0] ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } +} diff --git a/installer/test/java/org/apache/commons/cli/ValuesTest.java b/installer/test/java/org/apache/commons/cli/ValuesTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ValuesTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ValuesTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import java.util.Arrays; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ValuesTest extends TestCase +{ + /** CommandLine instance */ + private CommandLine _cmdline = null; + private Option _option = null; + + public static Test suite() { + return new TestSuite( ValuesTest.class ); + } + + public ValuesTest( String name ) + { + super( name ); + } + + public void setUp() + { + Options opts = new Options(); + + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "set -b"); + + opts.addOption("c", + "c", + false, + "toggle -c"); + + opts.addOption("d", + "d", + true, + "set -d"); + + OptionBuilder.withLongOpt( "e" ); + OptionBuilder.hasArgs(); + OptionBuilder.withDescription( "set -e "); + opts.addOption( OptionBuilder.create( 'e' ) ); + + opts.addOption("f", + "f", + false, + "jk"); + + OptionBuilder.withLongOpt( "g" ); + OptionBuilder.hasArgs( 2 ); + OptionBuilder.withDescription( "set -g"); + opts.addOption( OptionBuilder.create( 'g' ) ); + + OptionBuilder.withLongOpt( "h" ); + OptionBuilder.hasArgs( 2 ); + OptionBuilder.withDescription( "set -h"); + opts.addOption( OptionBuilder.create( 'h' ) ); + + OptionBuilder.withLongOpt( "i" ); + OptionBuilder.withDescription( "set -i"); + opts.addOption( OptionBuilder.create( 'i' ) ); + + OptionBuilder.withLongOpt( "j" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -j"); + OptionBuilder.withValueSeparator( '=' ); + opts.addOption( OptionBuilder.create( 'j' ) ); + + OptionBuilder.withLongOpt( "k" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -k"); + OptionBuilder.withValueSeparator( '=' ); + opts.addOption( OptionBuilder.create( 'k' ) ); + + OptionBuilder.withLongOpt( "m" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -m"); + OptionBuilder.withValueSeparator( ); + _option = OptionBuilder.create( 'm' ); + + opts.addOption( _option ); + + String[] args = new String[] { "-a", + "-b", "foo", + "--c", + "--d", "bar", + "-e", "one", "two", + "-f", + "arg1", "arg2", + "-g", "val1", "val2" , "arg3", + "-h", "val1", "-i", + "-h", "val2", + "-jkey=value", + "-j", "key=value", + "-kkey1=value1", + "-kkey2=value2", + "-mkey=value"}; + + CommandLineParser parser = new PosixParser(); + + try + { + _cmdline = parser.parse(opts,args); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void tearDown() + { + + } + + public void testShortArgs() + { + assertTrue( _cmdline.hasOption("a") ); + assertTrue( _cmdline.hasOption("c") ); + + assertNull( _cmdline.getOptionValues("a") ); + assertNull( _cmdline.getOptionValues("c") ); + } + + public void testShortArgsWithValue() + { + assertTrue( _cmdline.hasOption("b") ); + assertTrue( _cmdline.getOptionValue("b").equals("foo")); + assertTrue( _cmdline.getOptionValues("b").length == 1); + + assertTrue( _cmdline.hasOption("d") ); + assertTrue( _cmdline.getOptionValue("d").equals("bar")); + assertTrue( _cmdline.getOptionValues("d").length == 1); + } + + public void testMultipleArgValues() + { + _cmdline.getOptionValues("e"); + String[] values = new String[] { "one", "two" }; + assertTrue( _cmdline.hasOption("e") ); + assertTrue( _cmdline.getOptionValues("e").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("e") ) ); + } + + public void testTwoArgValues() + { + _cmdline.getOptionValues("g"); + String[] values = new String[] { "val1", "val2" }; + assertTrue( _cmdline.hasOption("g") ); + assertTrue( _cmdline.getOptionValues("g").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("g") ) ); + } + + public void testComplexValues() + { + _cmdline.getOptionValues("h"); + String[] values = new String[] { "val1", "val2" }; + assertTrue( _cmdline.hasOption("i") ); + assertTrue( _cmdline.hasOption("h") ); + assertTrue( _cmdline.getOptionValues("h").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("h") ) ); + } + + public void testExtraArgs() + { + String[] args = new String[] { "arg1", "arg2", "arg3" }; + assertTrue( _cmdline.getArgs().length == 3 ); + assertTrue( Arrays.equals( args, _cmdline.getArgs() ) ); + } + + public void testCharSeparator() + { + // tests the char methods of CommandLine that delegate to + // the String methods + String[] values = new String[] { "key", "value", "key", "value" }; + assertTrue( _cmdline.hasOption( "j" ) ); + assertTrue( _cmdline.hasOption( 'j' ) ); + assertTrue( _cmdline.getOptionValues( "j" ).length == 4); + assertTrue( _cmdline.getOptionValues( 'j' ).length == 4); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "j" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'j' ) ) ); + + values = new String[] { "key1", "value1", "key2", "value2" }; + assertTrue( _cmdline.hasOption( "k" ) ); + assertTrue( _cmdline.hasOption( 'k' ) ); + assertTrue( _cmdline.getOptionValues( "k" ).length == 4 ); + assertTrue( _cmdline.getOptionValues( 'k' ).length == 4 ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "k" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'k' ) ) ); + + values = new String[] { "key", "value" }; + assertTrue( _cmdline.hasOption( "m" ) ); + assertTrue( _cmdline.hasOption( 'm' ) ); + assertTrue( _cmdline.getOptionValues( "m" ).length == 2); + assertTrue( _cmdline.getOptionValues( 'm' ).length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "m" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'm' ) ) ); + } + + /** + * jkeyes - commented out this test as the new architecture + * breaks this type of functionality. I have left the test + * here in case I get a brainwave on how to resolve this. + */ + /* + public void testGetValue() + { + // the 'm' option + assertTrue( _option.getValues().length == 2 ); + assertEquals( _option.getValue(), "key" ); + assertEquals( _option.getValue( 0 ), "key" ); + assertEquals( _option.getValue( 1 ), "value" ); + + try { + assertEquals( _option.getValue( 2 ), "key" ); + fail( "IndexOutOfBounds not caught" ); + } + catch( IndexOutOfBoundsException exp ) { + + } + + try { + assertEquals( _option.getValue( -1 ), "key" ); + fail( "IndexOutOfBounds not caught" ); + } + catch( IndexOutOfBoundsException exp ) { + + } + } + */ +} diff --git a/installer/test/java/org/python/util/install/ChildProcessExample.java b/installer/test/java/org/python/util/install/ChildProcessExample.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChildProcessExample.java @@ -0,0 +1,32 @@ + +package org.python.util.install; + +/** + * An example child process that generates some output. + */ +public class ChildProcessExample { + + public ChildProcessExample() { + System.out.println("[ChildProcessExample] is now here."); + } + + public static void main(String args[]) { + int i = 0; + new ChildProcessExample(); + for (i = 0; i < 10; i++) { + System.out.println("[ChildProcessExample] printing to stdout " + i); + // occasionally print to stderr, too + if (i % 3 == 0) { + System.err.println("[ChildProcessExample] printing to stderr " + i); + } + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + System.out.println("[ChildProcessExample] Exiting"); + System.exit(0); + } + +} \ No newline at end of file diff --git a/installer/test/java/org/python/util/install/ChildProcessTest.java b/installer/test/java/org/python/util/install/ChildProcessTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChildProcessTest.java @@ -0,0 +1,80 @@ +package org.python.util.install; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import junit.framework.TestCase; + +public class ChildProcessTest extends TestCase { + + private final static String CLASS_NAME = "org.python.util.install.ChildProcessExample"; + + /** + * test a default child process + */ + public void testDefault() { + ChildProcess childProcess = new ChildProcess(); + String command[] = buildJavaCommand(CLASS_NAME); + childProcess.setCommand(command); + childProcess.setDebug(true); + int exitValue = childProcess.run(); + assertEquals("Expected child process to end normally instead of " + exitValue, 0, exitValue); + } + + /** + * test the child process with a timeout + */ + public void testTimeout() { + ChildProcess childProcess = new ChildProcess(); + String command[] = buildJavaCommand(CLASS_NAME); + childProcess.setCommand(command); + childProcess.setDebug(true); + childProcess.setTimeout(2000); // timeout to 2 seconds + int exitValue = childProcess.run(); + assertEquals("Expected child process to be destroyed instead of " + exitValue, + ChildProcess.DESTROYED_AFTER_TIMEOUT, + exitValue); + } + + /** + * test silent mode + */ + public void testSilent() throws IOException { + ChildProcess childProcess = new ChildProcess(); + String command[] = new String[] {"lwiklsl", "-siwK"}; + childProcess.setCommand(command); + childProcess.setDebug(false); + childProcess.setSilent(true); + ByteArrayOutputStream redirectedErr = new ByteArrayOutputStream(); + ByteArrayOutputStream redirectedOut = new ByteArrayOutputStream(); + int exitValue = 0; + PrintStream oldErr = System.err; + PrintStream oldOut = System.out; + try { + System.setErr(new PrintStream(redirectedErr)); + System.setOut(new PrintStream(redirectedOut)); + exitValue = childProcess.run(); + } finally { + System.setErr(oldErr); + System.setOut(oldOut); + } + assertTrue(0 != exitValue); + redirectedErr.flush(); + redirectedOut.flush(); + assertEquals(0, redirectedErr.size()); + assertEquals(0, redirectedOut.size()); + } + + // + // private methods + // + private String[] buildJavaCommand(String classAndArguments) { + String quote = ""; + if (System.getProperty("os.name", "unknown").toLowerCase().indexOf("windows") >= 0) { + quote = "\""; + } + String classpath = System.getProperty("java.class.path"); + return new String[] {"java", "-classpath", quote.concat(classpath).concat(quote), classAndArguments}; + } +} \ No newline at end of file diff --git a/installer/test/java/org/python/util/install/ChmodTest_Standalone.java b/installer/test/java/org/python/util/install/ChmodTest_Standalone.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChmodTest_Standalone.java @@ -0,0 +1,57 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; + +/** + * Helper class to test of 'chmod' on different platforms + */ +public class ChmodTest_Standalone { + + private static String _mode = "755"; // default mode + + public static void main(String[] args) { + // get mode from first argument, if present + if (args.length > 0) { + _mode = args[0]; + } + + // create an empty test file in the current directory + String curdir = System.getProperty("user.dir"); + File testFile = new File(curdir, "chmod.test"); + String path = testFile.getAbsolutePath(); + if (!testFile.exists()) { + try { + if (!testFile.createNewFile()) { + System.err.println(getPrefix() + "unable to create file " + path); + System.exit(1); + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + + // apply the chmod command on the test file + if (!testFile.exists()) { + System.err.println(getPrefix() + "unable to create file " + path); + System.exit(1); + } else { + String command[] = new String[] {"chmod", _mode, path}; + ChildProcess childProcess = new ChildProcess(command, 3000); + childProcess.setDebug(true); + int exitValue = childProcess.run(); + if (exitValue != 0) { + System.err.println(getPrefix() + "error during chmod"); + } else { + System.out.println(getPrefix() + "chmod command executed on " + path); + } + System.exit(exitValue); + } + } + + private static String getPrefix() { + return "[ChmodTest_Standalone] "; + } + +} diff --git a/installer/test/java/org/python/util/install/FileHelperTest.java b/installer/test/java/org/python/util/install/FileHelperTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/FileHelperTest.java @@ -0,0 +1,271 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; + +import junit.framework.TestCase; +import junit.runner.TestSuiteLoader; + +import org.python.util.install.driver.Autotest; + +public class FileHelperTest extends TestCase { + + private static final String JYTHON_TEST_TEMPLATE = "jython_test.template"; + + private static final String LOGO_GIF = "logo.gif"; + + private static final String JYTHON_SMALL_C_PNG = "jython_small_c.png"; + + public void testCreateTempDirectory_WasFile() throws IOException { + File file = File.createTempFile("some_prefix", ""); + assertTrue(file.exists()); + assertTrue(file.isFile()); + assertTrue(FileHelper.createTempDirectory(file)); + assertTrue(file.exists()); + assertTrue(file.isDirectory()); + } + + public void testCreateTempDirectory_AlreadyPresent() throws IOException { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + assertTrue(FileHelper.createTempDirectory(dir)); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + } + + public void testCreateTempDirectory() throws IOException { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + File tmpDir = new File(dir, "tmp"); + assertFalse(tmpDir.exists()); + try { + assertTrue(FileHelper.createTempDirectory(tmpDir)); + assertTrue(tmpDir.exists()); + assertTrue(dir.isDirectory()); + } finally { + if (tmpDir.exists()) { + assertTrue(tmpDir.delete()); + } + } + } + + public void testCreateTempDirectory_Failure() throws Exception { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + File tmpFile = new File(dir, "tmpFailure"); + assertFalse(tmpFile.exists()); + try { + tmpFile.createNewFile(); + assertTrue(tmpFile.exists()); + assertTrue(tmpFile.isFile()); + Lock lock = null; + try { + lock = new Lock(tmpFile); + boolean created = FileHelper.createTempDirectory(tmpFile); + if (Installation.isWindows()) { + // locking currently only effective on windows + assertFalse(created); + } else { + // change if there is a locking mechanism + assertTrue(created); + } + } finally { + if (lock != null) { + lock.release(); + } + } + } finally { + if (tmpFile.exists()) { + assertTrue(tmpFile.delete()); + } + } + } + + public void testRmdir() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + assertTrue(FileHelper.rmdir(dir)); + } + + public void testRmdir_Failure() throws Exception { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + Lock lock = null; + try { + lock = new Lock(jython_bat); + boolean removed = FileHelper.rmdir(dir); + if (Installation.isWindows()) { + // locking currently only effective on windows + assertFalse(removed); + } else { + // change if there is a locking mechanism + assertTrue(removed); + } + } finally { + if (lock != null) { + lock.release(); + } + assertTrue(FileHelper.rmdir(dir)); + } + } + + public void testReadAll() throws Exception { + File file = File.createTempFile("testReadAll", ""); + final String contents = new String("line1 \n line2 \n"); + FileHelper.write(file, contents); + String readContents = FileHelper.readAll(file); + assertEquals(contents, readContents); + } + + public void testReadAll_InputStream() throws Exception { + URL url = FileHelper.getRelativeURL(Autotest.class, JYTHON_TEST_TEMPLATE); + assertNotNull(url); + URI uri = new URI(url.toString()); + File file = new File(uri); + assertNotNull(file); + assertTrue(file.exists()); + String expectedContents = FileHelper.readAll(file); + InputStream is = FileHelper.getRelativeURLAsStream(Autotest.class, JYTHON_TEST_TEMPLATE); + assertNotNull(is); + String contents = FileHelper.readAll(is); + assertEquals(expectedContents, contents); + // now from a .jar + is = FileHelper.getRelativeURLAsStream(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(is); + contents = FileHelper.readAll(is); + assertNotNull(contents); + assertEquals(964, contents.length()); + assertTrue(contents.startsWith("GIF89a&")); + } + + public void testReadAll_NonExisting() { + String readContents = null; + try { + readContents = FileHelper.readAll(new File("_non_existing")); + fail("FileNotFoundException expected"); + } catch (IOException e) { + assertNull(readContents); + } + } + + public void testGetRelativeURL() { + URL url = FileHelper.getRelativeURL(Installation.class, JYTHON_SMALL_C_PNG); + assertNotNull(url); + assertTrue(url.getPath().endsWith("org/python/util/install/".concat(JYTHON_SMALL_C_PNG))); + // now from a .jar + url = FileHelper.getRelativeURL(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(url); + assertTrue(url.getPath().endsWith("!/junit/runner/".concat(LOGO_GIF))); + } + + public void testGetRelativeURLAsStream() throws IOException { + InputStream is = FileHelper.getRelativeURLAsStream(Installation.class, JYTHON_SMALL_C_PNG); + assertNotNull(is); + try { + assertTrue(is.read() >= 0); + } finally { + is.close(); + } + // now from a .jar + is = FileHelper.getRelativeURLAsStream(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(is); + try { + assertTrue(is.read() >= 0); + } finally { + is.close(); + } + } + + public void testWrite() throws IOException { + File file = new File("testWrite"); + assertFalse(file.exists()); + try { + final String contents = new String("line1 \n line2 \n"); + FileHelper.write(file, contents); + assertTrue(file.exists()); + String readContents = FileHelper.readAll(file); + assertEquals(contents, readContents); + } finally { + if (file.exists()) { + assertTrue(file.delete()); + } + } + } + + public void testWrite_Existing() throws IOException { + File file = File.createTempFile("testWrite", ""); + assertTrue(file.exists()); + final String firstContents = "first dummy contents"; + FileHelper.write(file, firstContents); + assertEquals(firstContents, FileHelper.readAll(file)); + final String realContents = new String("line1 \n line2 \n"); + FileHelper.write(file, realContents); + assertEquals(realContents, FileHelper.readAll(file)); + } + + /** + * A poor man's file lock (does work on windows only) + */ + public static class Lock { + + private final FileOutputStream _fos; + + /** + * acquire a file lock + * + * @param file + * The file to be locked + * @throws FileNotFoundException + */ + public Lock(File file) throws FileNotFoundException { + _fos = new FileOutputStream(file); + } + + /** + * release the file lock + * + * @throws IOException + */ + public void release() throws IOException { + if (_fos != null) { + _fos.close(); + } + } + } +} diff --git a/installer/test/java/org/python/util/install/FrameInstallerTest.java b/installer/test/java/org/python/util/install/FrameInstallerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/FrameInstallerTest.java @@ -0,0 +1,88 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +import junit.framework.TestCase; + +public class FrameInstallerTest extends TestCase { + + public void testInitDefaultJava() { + FrameInstaller.initDefaultJava(); + JavaVersionInfo vInfo = FrameInstaller.getJavaVersionInfo(); + assertNotNull(vInfo); + String version = vInfo.getVersion(); + assertNotNull(version); + assertTrue(version.length() > 0); + String specificationVersion = vInfo.getSpecificationVersion(); + assertNotNull(specificationVersion); + assertTrue(specificationVersion.length() > 0); + String vendor = vInfo.getVendor(); + assertNotNull(vendor); + assertTrue(vendor.length() > 0); + } + + public void testJavaVersionInfo() { + String version = "1;2;3"; + String vendor = "jython [macrosystems]"; + String specificationVersion = "@spec 1,4"; + + JavaVersionInfo vInfo = new JavaVersionInfo(); + vInfo.setVersion(version); + vInfo.setVendor(vendor); + vInfo.setSpecificationVersion(specificationVersion); + + FrameInstaller.setJavaVersionInfo(vInfo); + JavaVersionInfo returnedInfo = FrameInstaller.getJavaVersionInfo(); + + assertNotNull(returnedInfo); + assertEquals(version, returnedInfo.getVersion()); + assertEquals(vendor, returnedInfo.getVendor()); + assertEquals(specificationVersion, returnedInfo.getSpecificationVersion()); + } + + public void testInstallationType() { + InstallationType installationType = new InstallationType(); + installationType.addLibraryModules(); + installationType.removeDemosAndExamples(); + installationType.removeDocumentation(); + installationType.addSources(); + + FrameInstaller.setInstallationType(installationType); + InstallationType returnedType = FrameInstaller.getInstallationType(); + + assertNotNull(returnedType); + assertTrue(returnedType.installLibraryModules()); + assertFalse(returnedType.installDemosAndExamples()); + assertFalse(returnedType.installDocumentation()); + assertTrue(returnedType.installSources()); + } + + public void testStandalone() { + InstallationType installationType = new InstallationType(); + installationType.setStandalone(); + assertTrue(installationType.installLibraryModules()); + assertFalse(installationType.installDemosAndExamples()); + assertFalse(installationType.installDocumentation()); + assertFalse(installationType.installSources()); + + FrameInstaller.setInstallationType(installationType); + InstallationType returnedType = FrameInstaller.getInstallationType(); + + assertNotNull(returnedType); + assertTrue(returnedType.isStandalone()); + assertTrue(returnedType.installLibraryModules()); + assertFalse(returnedType.installDemosAndExamples()); + assertFalse(returnedType.installDocumentation()); + assertFalse(returnedType.installSources()); + } + + public void testSetGetJavaHomeHandler() { + assertNotNull(FrameInstaller.getJavaHomeHandler()); + JavaHomeHandler handler1 = new JavaHomeHandler(); + JavaHomeHandler handler2 = new JavaHomeHandler("some/dir"); + FrameInstaller.setJavaHomeHandler(handler1); + assertEquals(handler1, FrameInstaller.getJavaHomeHandler()); + FrameInstaller.setJavaHomeHandler(handler2); + assertEquals(handler2, FrameInstaller.getJavaHomeHandler()); + } +} diff --git a/installer/test/java/org/python/util/install/InstallationTest.java b/installer/test/java/org/python/util/install/InstallationTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallationTest.java @@ -0,0 +1,107 @@ +package org.python.util.install; + +import java.io.File; + +import org.python.util.install.Installation.JavaVersionInfo; + +import junit.framework.TestCase; + +public class InstallationTest extends TestCase { + + public void testGetExternalJavaVersion() { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.NORMAL_RETURN, versionInfo.getErrorCode()); + assertEquals("", versionInfo.getReason()); + assertTrue(versionInfo.getVersion().length() > 0); + assertTrue(versionInfo.getSpecificationVersion().length() > 0); + assertTrue(versionInfo.getVersion().startsWith(versionInfo.getSpecificationVersion())); + assertNotNull(versionInfo.getVendor()); + assertNotSame("", versionInfo.getVendor()); + } + + public void testGetExternalJavaVersionWithError() { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler("non_existing/home"); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + String reason = versionInfo.getReason(); + assertTrue(reason.indexOf("invalid") >= 0); + } + + public void testGetExternalJavaVersionNoBinDirectory() { + File wrongHome = new File(System.getProperty("user.home")); + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(wrongHome.getAbsolutePath()); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + String reason = versionInfo.getReason(); + assertTrue(reason.indexOf("invalid") >= 0); + } + + public void testGetExternalJavaVersionNoJavaInBinDirectory() { + File wrongHome = new File(System.getProperty("user.home")); + File binDir = new File(wrongHome, "bin"); + assertFalse(binDir.exists()); + try { + assertTrue(binDir.mkdirs()); + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(wrongHome.getAbsolutePath()); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + assertTrue(versionInfo.getReason().indexOf("invalid") >= 0); + } finally { + if (binDir.exists()) { + binDir.delete(); + } + } + } + + public void testIsValidJavaVersion() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + + javaVersionInfo.setSpecificationVersion("1.1.9"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.2"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.3"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.4"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.5"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.6"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.7"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + } + + public void testGetJavaSpecificationVersion() { + String specificationVersion = "1.4.2"; + assertEquals(14, Installation.getJavaSpecificationVersion(specificationVersion)); + specificationVersion = "1.5.0"; + assertEquals(15, Installation.getJavaSpecificationVersion(specificationVersion)); + specificationVersion = "1.6.0"; + assertEquals(16, Installation.getJavaSpecificationVersion(specificationVersion)); + } + + public void testIsGNUJava() { + assertFalse(Installation.isGNUJava()); + String originalVmName = System.getProperty(Installation.JAVA_VM_NAME); + try { + // fake GNU java + System.setProperty(Installation.JAVA_VM_NAME, "GNU libgcj"); + assertTrue(Installation.isGNUJava()); + } finally { + System.setProperty(Installation.JAVA_VM_NAME, originalVmName); + assertFalse(Installation.isGNUJava()); + } + } + + public void testGetDefaultJavaVersion() { + JavaVersionInfo info = Installation.getDefaultJavaVersion(); + assertNotNull(info); + assertEquals(Installation.NORMAL_RETURN, info.getErrorCode()); + String specVersion = info.getSpecificationVersion(); + assertNotNull(specVersion); + assertTrue(specVersion.length() >= 3); + } + +} diff --git a/installer/test/java/org/python/util/install/InstallationTypeTest.java b/installer/test/java/org/python/util/install/InstallationTypeTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallationTypeTest.java @@ -0,0 +1,119 @@ +package org.python.util.install; + +import junit.framework.TestCase; + +// test checkin +public class InstallationTypeTest extends TestCase { + + private InstallationType _type; + + protected void setUp() { + _type = new InstallationType(); + } + + public void testConstruction() { + assertTrue(_type.isStandard()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testStandard() { + _type.setStandard(); + assertTrue(_type.isStandard()); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testMinimum() { + assertFalse(_type.isMinimum()); + _type.setMinimum(); + assertTrue(_type.isMinimum()); + assertFalse(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testAll() { + assertFalse(_type.isAll()); + _type.setAll(); + assertTrue(_type.isAll()); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testStandalone() { + assertFalse(_type.isStandalone()); + _type.setStandalone(); + assertTrue(_type.isStandalone()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertTrue(_type.isPredefined()); + + // sure to handle this as follows? + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + } + + public void testAddRemove() { + _type.removeDocumentation(); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.removeDemosAndExamples(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.addSources(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.addDocumentation(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + } + +} diff --git a/installer/test/java/org/python/util/install/InstallerCommandLineTest.java b/installer/test/java/org/python/util/install/InstallerCommandLineTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallerCommandLineTest.java @@ -0,0 +1,637 @@ +package org.python.util.install; + +import java.io.File; + +import junit.framework.TestCase; + +public class InstallerCommandLineTest extends TestCase { + + public void testValidArguments() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--directory", "c:/temp" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--type", "all" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-t", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type", "standard" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--verbose" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-A" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--autotest" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + } + + public void testInvalidArguments() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "--one" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--one argOne" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--one", "--two", "--three" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-o" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type", "weird" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testMissingArgument() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "--directory" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--type" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testUnknownArgument() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "yeah" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "yeah" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "yeah", "yoyo" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--type", "takatuka" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testOptionGroups() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-s", "-c" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-s", "-A" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-c", "-A" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "--console" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "--autotest" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--console", "--autotest" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-?", "-h" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-?", "--help" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testSilent() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-s" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); // expect required directory in silent mode + + args = new String[] { "-s", "-d", "/tmp" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + } + + public void testConsole() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); + } + + public void testGui() { + String[] args; + InstallerCommandLine commandLine; + + // normal gui startup without any arguments + assertTrue(Installation.isGuiAllowed()); + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasConsoleOption()); + assertFalse(commandLine.hasSilentOption()); + } + + /** + * simulate startup on a headless system (auto-switch to console mode) + */ + public void testHeadless() { + String[] args; + InstallerCommandLine commandLine; + boolean originalHeadless = Boolean.getBoolean(Installation.HEADLESS_PROPERTY_NAME); + try { + if (!originalHeadless) { + System.setProperty(Installation.HEADLESS_PROPERTY_NAME, "true"); + } + assertFalse(Installation.isGuiAllowed()); + + // without any arguments + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + + // with one argument + args = new String[] {"-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + + // with more arguments + args = new String[] {"-v", "-t", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + assertTrue(commandLine.hasTypeOption()); + InstallationType type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isMinimum()); + + // silent should override! + args = new String[] {"-v", "-s", "-d", "some_dir"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + File dir = commandLine.getTargetDirectory(); + assertNotNull(dir); + assertEquals("some_dir", dir.getName()); + + // -A (autotest) should override as well + args = new String[] {"-A"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertFalse(commandLine.hasSilentOption()); + + // console aready present should be no problem + args = new String[] {"-c", "-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); + assertFalse(commandLine.hasSilentOption()); + } finally { + if (!originalHeadless) { + System.setProperty(Installation.HEADLESS_PROPERTY_NAME, "false"); + assertTrue(Installation.isGuiAllowed()); + } + } + } + + public void testGNUSwitchToConsole() { + String originalVmName = System.getProperty(Installation.JAVA_VM_NAME); + try { + // fake GNU java + System.setProperty(Installation.JAVA_VM_NAME, "GNU libgcj"); + assertTrue(Installation.isGNUJava()); + String[] args; + InstallerCommandLine commandLine; + // expect auto switch + args = new String[] {"-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + // expect no auto switch + args = new String[] {"-s", "-d", "some_dir"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertFalse(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertTrue(commandLine.hasDirectoryOption()); + File dir = commandLine.getTargetDirectory(); + assertNotNull(dir); + assertEquals("some_dir", dir.getName()); + } finally { + System.setProperty(Installation.JAVA_VM_NAME, originalVmName); + assertFalse(Installation.isGNUJava()); + } + } + + + public void testDirectory() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-d", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + + args = new String[] { "-s", "--directory", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + } + + public void testType() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "all" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isAll()); + + args = new String[] { "--type", "standard" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isStandard()); + + args = new String[] { "--type", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isMinimum()); + + args = new String[] { "--type", "standalone" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isStandalone()); + + assertFalse(commandLine.getJavaHomeHandler().isDeviation()); + } + + public void testInclude() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "minimum", "-i", "mod" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "-i", "mod", "demo" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "-i", "mod", "demo", "doc" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "--include", "mod", "demo", "doc", "src" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-i", "modulo" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testExclude() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "all", "-e", "mod" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "-e", "mod", "demo" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "-e", "mod", "demo", "doc" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "--exclude", "mod", "demo", "doc", "src" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "--exclude", "sources" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testJavaHomeHandler() { + String[] args; + InstallerCommandLine commandLine; + final String javaHomeName = "java/home/dir"; + + args = new String[] { "-j", javaHomeName }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasJavaHomeOption()); + JavaHomeHandler javaHomeHandler = commandLine.getJavaHomeHandler(); + assertNotNull(javaHomeHandler); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + + args = new String[] { "--jre", javaHomeName }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasJavaHomeOption()); + javaHomeHandler = commandLine.getJavaHomeHandler(); + assertNotNull(javaHomeHandler); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + + assertNull(commandLine.getTargetDirectory()); + } + + public void testExamples() { + String[] args; + InstallerCommandLine commandLine; + final String javaHomeName = "java/home/dir"; + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); + + args = new String[] { "-s", "-d", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + + args = new String[] { "-s", "-d", "dir", "-t", "standard", "-j", javaHomeName, "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + assertTrue(commandLine.hasTypeOption()); + assertNotNull(commandLine.getInstallationType()); + assertTrue(commandLine.getInstallationType().installDemosAndExamples()); + assertTrue(commandLine.getInstallationType().installDocumentation()); + assertTrue(commandLine.getInstallationType().installLibraryModules()); + assertFalse(commandLine.getInstallationType().installSources()); + assertTrue(commandLine.hasJavaHomeOption()); + JavaHomeHandler javaHomeHandler = commandLine.getJavaHomeHandler(); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + assertTrue(commandLine.hasVerboseOption()); + + args = new String[] { "-s", "-d", "dir", "-t", "standard", "-e", "doc", "demo", "-i", "src", "-j", javaHomeName, "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + assertTrue(commandLine.hasTypeOption()); + assertNotNull(commandLine.getInstallationType()); + assertFalse(commandLine.getInstallationType().installDemosAndExamples()); + assertFalse(commandLine.getInstallationType().installDocumentation()); + assertTrue(commandLine.getInstallationType().installLibraryModules()); + assertTrue(commandLine.getInstallationType().installSources()); + assertTrue(commandLine.hasJavaHomeOption()); + javaHomeHandler = commandLine.getJavaHomeHandler(); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + assertTrue(commandLine.hasVerboseOption()); + } + + public void testHelp() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasHelpOption()); + + args = new String[] { "--help" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + args = new String[] { "-h" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + args = new String[] { "-?" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + // now print the help + commandLine.printHelp(); + } + + public void testHasVerboseOptionInArgs() { + String[] args = new String[0]; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "b", "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", InstallerCommandLine.VERBOSE_SHORT, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "-" + InstallerCommandLine.VERBOSE_SHORT, "c"}; + assertTrue(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", InstallerCommandLine.VERBOSE_LONG, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "-" + InstallerCommandLine.VERBOSE_LONG, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "--" + InstallerCommandLine.VERBOSE_LONG, "c"}; + assertTrue(InstallerCommandLine.hasVerboseOptionInArgs(args)); + } + +} diff --git a/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java b/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java @@ -0,0 +1,114 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; + +import junit.framework.TestCase; + +public class JavaHomeHandlerTest extends TestCase { + + private static final String JAVA_HOME = JavaHomeHandler.JAVA_HOME; + + private static final String JAVA = "java"; + + private static final String SOME_WEIRD_HOME = "some/weird/home"; + + private String _originalJavaHome; + + @Override + protected void setUp() throws Exception { + JavaHomeHandler.reset(); + _originalJavaHome = System.getProperty(JAVA_HOME); + } + + @Override + protected void tearDown() throws Exception { + System.setProperty(JAVA_HOME, _originalJavaHome); + } + + public void testGetExecutableName() throws IOException { + String executable = new JavaHomeHandler().getExecutableName(); + assertNotNull(executable); + assertTrue(executable.length() > JAVA.length()); + String homePath = createTempHome().getAbsolutePath(); + executable = new JavaHomeHandler(homePath).getExecutableName(); + assertTrue(executable.length() > JAVA.length()); + assertTrue(executable.indexOf(homePath) >= 0); + System.setProperty(JAVA_HOME, homePath); + executable = new JavaHomeHandler().getExecutableName(); + assertTrue(executable.length() > JAVA.length()); + assertTrue(executable.indexOf(homePath) >= 0); + } + + public void testGetExecutableName_NonExisting() { + String executable = new JavaHomeHandler(SOME_WEIRD_HOME).getExecutableName(); + assertEquals(JAVA, executable); // fallback + System.setProperty(JAVA_HOME, SOME_WEIRD_HOME); + executable = new JavaHomeHandler().getExecutableName(); + assertEquals(JAVA, executable); // fallback + } + + public void testCreateJavaHomeHandler() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(); + assertNotNull(handler); + System.setProperty(JAVA_HOME, SOME_WEIRD_HOME); + handler = new JavaHomeHandler(); + assertNotNull(handler); + System.setProperty(JAVA_HOME, createTempHome().getAbsolutePath()); + handler = new JavaHomeHandler(); + assertNotNull(handler); + } + + public void testCreateHandler_Deviation() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(SOME_WEIRD_HOME); + assertNotNull(handler); + handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertNotNull(handler); + } + + public void testIsDeviation() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertTrue(handler.isDeviation()); + handler = new JavaHomeHandler(); + assertFalse(handler.isDeviation()); + handler = new JavaHomeHandler(System.getProperty(JAVA_HOME)); + assertFalse(handler.isDeviation()); + } + + public void testGetJavaHome() throws IOException { + String tempHome = createTempHome().getAbsolutePath(); + JavaHomeHandler handler = new JavaHomeHandler(tempHome); + String home = handler.getHome().getAbsolutePath(); + assertEquals(tempHome, home); + try { + handler = new JavaHomeHandler(SOME_WEIRD_HOME); + } catch (InstallerException ie) { + assertEquals("no valid java home", ie.getMessage()); + } + } + + public void testIsValidJavaHome() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(SOME_WEIRD_HOME); + assertFalse(handler.isValidHome()); + handler = new JavaHomeHandler(); + assertTrue(handler.isValidHome()); + handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertTrue(handler.isValidHome()); + } + + private File createTempHome() throws IOException { + File home = File.createTempFile("JavaHomeHandler", "Test"); + assertTrue(FileHelper.createTempDirectory(home)); + File binDir = new File(home, "bin"); + assertTrue(binDir.mkdirs()); + String executableName = JAVA; + if (Installation.isWindows()) { + executableName = executableName.concat(".exe"); + } + File java = new File(binDir, executableName); + FileHelper.write(java, "dummy"); + assertTrue(java.exists()); + assertTrue(java.isFile()); + return home; + } +} diff --git a/installer/test/java/org/python/util/install/JavaTest_Standalone.java b/installer/test/java/org/python/util/install/JavaTest_Standalone.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/JavaTest_Standalone.java @@ -0,0 +1,32 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +/** + * Helper class to test an external java version + */ +public class JavaTest_Standalone { + + public static void main(String[] args) { + if (args.length > 0) { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(args[0]); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (versionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + System.err.println(versionInfo.getReason()); + } else { + System.out.println(getPrefix() + "java version:" + versionInfo.getVersion()); + System.out.println(getPrefix() + "java spec version:" + versionInfo.getSpecificationVersion()); + } + System.exit(versionInfo.getErrorCode()); + } else { + System.err.println(getPrefix() + "missing argument: please specify the java home directory " + + "(/bin directory assumed below)"); + System.exit(1); + } + } + + private static String getPrefix() { + return "[JavaTest_Standalone] "; + } + +} diff --git a/installer/test/java/org/python/util/install/StandalonePackagerTest.java b/installer/test/java/org/python/util/install/StandalonePackagerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/StandalonePackagerTest.java @@ -0,0 +1,183 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import junit.framework.TestCase; + +public class StandalonePackagerTest extends TestCase { + private File _sourceJarFile; + private File _targetJarFile; + + private File _contentFile; + private File _nextContentFile; + private File _additionalFile; + + private File _contentDir; + private File _additionalDir; + + private File _jarDir; + + protected void setUp() throws Exception { + _jarDir = File.createTempFile("jarDir", ""); + assertTrue(FileHelper.createTempDirectory(_jarDir)); + _targetJarFile = new File(_jarDir, "target.jar"); + _sourceJarFile = new File(_jarDir, "source.jar"); + + _contentDir = File.createTempFile("content", ""); + assertTrue(FileHelper.createTempDirectory(_contentDir)); + _contentFile = new File(_contentDir, "content.file"); + _contentFile.createNewFile(); + assertTrue(_contentFile.exists()); + + createSourceJar(); + } + + protected void tearDown() throws Exception { + if (_sourceJarFile != null) { + _sourceJarFile.delete(); + } + if (_targetJarFile != null) { + _targetJarFile.delete(); + } + if (_contentFile != null) { + _contentFile.delete(); + } + if (_nextContentFile != null) { + _nextContentFile.delete(); + } + if (_additionalFile != null) { + _additionalFile.delete(); + } + if (_contentDir != null) { + _contentDir.delete(); + } + if (_additionalDir != null) { + _additionalDir.delete(); + } + if (_jarDir != null) { + _jarDir.delete(); + } + } + + /** + * test the static method emptyDir() + */ + public void testEmptyDir() throws Exception { + File tempContentFile = new File(_contentDir, "temp"); + tempContentFile.createNewFile(); + assertTrue(tempContentFile.exists()); + File tempDir = new File(_contentDir, "tempDir"); + assertTrue(FileHelper.createTempDirectory(tempDir)); + + StandalonePackager.emptyDirectory(_contentDir, _contentFile); + assertTrue(_contentFile.exists()); + assertFalse(tempContentFile.exists()); + assertFalse(tempDir.exists()); + assertEquals(1, _contentDir.list().length); + } + + /** + * test adding a jar file, a directory, and another single file + */ + public void testAdd_Jar_Directory_File() throws IOException { + createAdditionalDirectory(); + _nextContentFile = File.createTempFile("nextContent.file", ""); + _nextContentFile.createNewFile(); + assertTrue(_nextContentFile.exists()); + + StandalonePackager packager = new StandalonePackager(_targetJarFile); + try { + packager.addJarFile(_sourceJarFile); + packager.addFullDirectory(_additionalDir); + packager.addFile(_nextContentFile, null); + } finally { + packager.close(); + } + + assertTrue(_targetJarFile.exists()); + + Map mandatoryEntries = new HashMap(8); + mandatoryEntries.put(_contentDir.getName(), "dir"); + mandatoryEntries.put(_contentFile.getName(), "file"); + mandatoryEntries.put(_nextContentFile.getName(), "file"); + mandatoryEntries.put(_additionalDir.getName(), "dir"); + mandatoryEntries.put(_additionalFile.getName(), "file"); + mandatoryEntries.put("META-INF", "dir"); + mandatoryEntries.put("MANIFEST.MF", "file"); + + JarFile targetJarFile = new JarFile(_targetJarFile); + try { + Enumeration entries = targetJarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String name; + String entryName = entry.getName(); + int slashIndex = entryName.indexOf("/"); + if (slashIndex >= 0) { + // handle directory + name = entryName.substring(slashIndex + 1); + String dirName = entryName.substring(0, slashIndex); + assertTrue(mandatoryEntries.containsKey(dirName)); + assertEquals("dir", mandatoryEntries.get(dirName)); + mandatoryEntries.remove(dirName); + } else { + name = entryName; + } + if (mandatoryEntries.containsKey(name)) { + assertEquals("file", (String) mandatoryEntries.get(name)); + assertFalse(entry.isDirectory()); + mandatoryEntries.remove(name); + } + } + assertTrue(mandatoryEntries.size() == 0); + assertNotNull(targetJarFile.getManifest()); + } finally { + targetJarFile.close(); + } + } + + private void createSourceJar() throws FileNotFoundException, IOException { + Manifest manifest = new Manifest(); + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(_sourceJarFile), manifest); + addFile(_contentFile, _contentDir, jarOut); + jarOut.close(); + } + + private void createAdditionalDirectory() throws IOException { + _additionalDir = File.createTempFile("additional", ""); + assertTrue(FileHelper.createTempDirectory(_additionalDir)); + + _additionalFile = new File(_additionalDir, "additional.file"); + _additionalFile.createNewFile(); + assertTrue(_additionalFile.exists()); + } + + private void addFile(File file, File parentDir, JarOutputStream jarOut) throws IOException { + byte[] buffer = new byte[1024]; + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + String jarEntryName = parentDir.getName() + "/" + file.getName(); + jarOut.putNextEntry(new JarEntry(jarEntryName)); + for (int read = 0; read != -1; read = inputStream.read(buffer)) + jarOut.write(buffer, 0, read); + jarOut.closeEntry(); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + +} diff --git a/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java b/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java @@ -0,0 +1,277 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class StartScriptGeneratorTest extends TestCase { + + private static final String AT_DATE = "@DATE"; + + private static final String WIN_CR_LF = StartScriptGenerator.WIN_CR_LF; + + private StartScriptGenerator _generator; + + private File _targetDir; + + protected void setUp() throws Exception { + String userDirName = System.getProperty("user.dir"); // only true in eclipse ? + File userDir = new File(userDirName); + File parentDir = userDir.getParentFile(); + assertTrue(parentDir.exists()); + _targetDir = new File(parentDir, "jython"); + if (!_targetDir.exists()) { + _targetDir = new File(parentDir, "jython-trunk"); + } + assertTrue(_targetDir.exists()); + assertTrue(_targetDir.isDirectory()); + _targetDir = new File(_targetDir, "src"); + _targetDir = new File(_targetDir, "shell"); + assertTrue(_targetDir.exists()); + assertTrue(_targetDir.isDirectory()); + _generator = new StartScriptGenerator(_targetDir, new JavaHomeHandler()); + } + + // TODO: test on Solaris + public void testUnix() throws IOException { + _generator.setFlavour(StartScriptGenerator.UNIX_FLAVOUR); + StringBuffer buf = new StringBuffer(100); + buf.append("#!/usr/bin/env bash\n"); + buf.append("\n"); + buf.append("# This file was generated by the Jython installer\n"); + buf.append("# Created on " + AT_DATE + " by " + System.getProperty("user.name") + "\n"); + buf.append("\n"); + buf.append("JAVA_HOME=\""); + buf.append(System.getProperty("java.home")); + buf.append("\"\n"); + buf.append("JYTHON_HOME_FALLBACK=\""); + buf.append(_targetDir.getAbsolutePath()); + buf.append("\"\n"); + // some rudimentary tests - feel free to do more + String start = buf.toString().replaceAll(AT_DATE, new Date().toString()); + String unixScript = _generator.getJythonScript(StartScriptGenerator.UNIX_FLAVOUR); + assertTrue(unixScript.startsWith(start)); + assertTrue(unixScript.length() > 3500); + assertTrue(unixScript.indexOf("-Dpython.home=") > start.length()); + assertTrue(unixScript.indexOf("-Dpython.executable=") > start.length()); + // no hard coding of JYTHON_HOME + int jythonHomeIndex = unixScript.indexOf("if [ -z \"$JYTHON_HOME\" ] ; then"); + assertTrue(jythonHomeIndex >= 0); + int definitionIndex = unixScript.indexOf("JYTHON_HOME="); + assertTrue(definitionIndex > jythonHomeIndex || definitionIndex < 0); + } + + public void testWindows() throws IOException { + StringBuffer winBuf = new StringBuffer(100); + winBuf.append("@echo off" + WIN_CR_LF); + winBuf.append("rem This file was generated by the Jython installer" + WIN_CR_LF); + winBuf.append("rem Created on " + AT_DATE + " by " + System.getProperty("user.name") + "" + + WIN_CR_LF); + winBuf.append(WIN_CR_LF); + winBuf.append("set JAVA_HOME=\""); + winBuf.append(System.getProperty("java.home")); + winBuf.append("\""); + winBuf.append(WIN_CR_LF); + winBuf.append("set JYTHON_HOME_FALLBACK=\""); + winBuf.append(_targetDir.getAbsolutePath()); + winBuf.append("\""); + winBuf.append(WIN_CR_LF); + // some rudimentary tests - feel free to do more + String start = winBuf.toString().replaceAll(AT_DATE, new Date().toString()); + String winScript = _generator.getJythonScript(StartScriptGenerator.WINDOWS_FLAVOUR); + assertTrue(winScript.startsWith(start)); + assertTrue(winScript.length() > 3500); + assertTrue(winScript.indexOf("if not \"%_TRIMMED_JAVA_HOME%\"==\"\"") > start.length()); + assertTrue(winScript.indexOf("-Dpython.home=") > start.length()); + assertTrue(winScript.indexOf("-Dpython.executable=") > start.length()); + // no hard coding of JYTHON_HOME + int jythonHomeIndex = winScript.indexOf("if not \"%_TRIMMED_JYTHON_HOME%\"==\"\""); + assertTrue(jythonHomeIndex >= 0); + int definitionIndex = winScript.indexOf("set JYTHON_HOME="); + assertTrue(definitionIndex > jythonHomeIndex || definitionIndex < 0); + } + + public void testFlavour() { + int expectedFlavour; + expectedFlavour = StartScriptGenerator.UNIX_FLAVOUR; + _generator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, _generator.getFlavour()); + expectedFlavour = StartScriptGenerator.BOTH_FLAVOUR; + _generator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, _generator.getFlavour()); + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(new File("dummy"), + new JavaHomeHandler("dummy"), + false); + expectedFlavour = StartScriptGenerator.WINDOWS_FLAVOUR; + testGenerator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, testGenerator.getFlavour()); + expectedFlavour = StartScriptGenerator.UNIX_FLAVOUR; + testGenerator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, testGenerator.getFlavour()); + testGenerator = new TestStartScriptGenerator(new File("dummy"), + new JavaHomeHandler("dummy"), + true); + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + assertEquals(StartScriptGenerator.BOTH_FLAVOUR, testGenerator.getFlavour()); + } + + public void testWindowsFlavour() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // windows flavour + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + false); + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); // 1 file plus the /bin subdirectory + HashSet fileNamesSet = new HashSet(2); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython.bat")); + fileNames = bin.list(); + assertEquals(1, fileNames.length); + assertEquals("jython.bat", fileNames[0]); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + public void testUnixFlavour() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // unix flavour + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + false); + testGenerator.setFlavour(StartScriptGenerator.UNIX_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); // 1 file plus the /bin subdirectory + HashSet fileNamesSet = new HashSet(2); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython")); + fileNames = bin.list(); + assertEquals(1, fileNames.length); + assertEquals("jython", fileNames[0]); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + public void testBothFlavours() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // both flavours + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + true); + // test generator constructor timing problem: do set the flavour once again + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(3, fileNamesLength); // 2 files plus the /bin subdirectory + Set fileNamesSet = new HashSet(4); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython")); + assertTrue(fileNamesSet.contains("jython.bat")); + fileNames = bin.list(); + fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); + fileNamesSet = new HashSet(4); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("jython")); + assertTrue(fileNamesSet.contains("jython.bat")); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + class TestStartScriptGenerator extends StartScriptGenerator { + + private boolean _hasBothFlavours; + + public TestStartScriptGenerator(File targetDirectory, + JavaHomeHandler javaHomeHandler, + boolean hasBothFlavours) { + super(targetDirectory, javaHomeHandler); + _hasBothFlavours = hasBothFlavours; + } + + protected boolean hasUnixlikeShell() { + return _hasBothFlavours; + } + } +} diff --git a/installer/test/java/org/python/util/install/UnicodeSequencesTest.java b/installer/test/java/org/python/util/install/UnicodeSequencesTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/UnicodeSequencesTest.java @@ -0,0 +1,34 @@ +package org.python.util.install; + +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class UnicodeSequencesTest extends TestCase { + + private static Set _latin1Encodings; + + public void testUmlaute() { + String fileEncoding = System.getProperty("file.encoding", "unknown"); + if (getLatin1Encodings().contains(fileEncoding)) { + assertEquals("?", UnicodeSequences.a2); + assertEquals("?", UnicodeSequences.A2); + assertEquals("?", UnicodeSequences.o2); + assertEquals("?", UnicodeSequences.O2); + assertEquals("?", UnicodeSequences.u2); + assertEquals("?", UnicodeSequences.U2); + } + } + + private static Set getLatin1Encodings() { + if (_latin1Encodings == null) { + _latin1Encodings = new HashSet(3); + _latin1Encodings.add("ISO-LATIN-1"); + _latin1Encodings.add("ISO-8859-1"); + _latin1Encodings.add("Cp1252"); + } + return _latin1Encodings; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/AutotestTest.java b/installer/test/java/org/python/util/install/driver/AutotestTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/AutotestTest.java @@ -0,0 +1,78 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.InstallerCommandLine; + +import junit.framework.TestCase; + +public class AutotestTest extends TestCase { + + private Autotest _autotest; + + protected void setUp() throws Exception { + InstallerCommandLine commandLine = new InstallerCommandLine(); + commandLine.setArgs(new String[0]); + _autotest = new SilentAutotest(commandLine); + } + + public void testCreateDirectories() { + File rootDir = Autotest.getRootDir(); + File targetDir = _autotest.getTargetDir(); + assertNotNull(rootDir); + verifyDir(rootDir, false); + assertNotNull(targetDir); + verifyDir(targetDir, true); + assertEquals(rootDir, targetDir.getParentFile()); + } + + public void testCommandLineArgs() { + String[] args = new String[] { "-x", "-y", "-z" }; + _autotest.setCommandLineArgs(args); + int len = _autotest.getCommandLineArgs().length; + assertEquals(args.length, len); + for (int i = 0; i < args.length; i++) { + assertEquals(args[i], _autotest.getCommandLineArgs()[i]); + } + } + + public void testAddArgument() { + String[] args = new String[] { "-x", "-y", "-z" }; + _autotest.setCommandLineArgs(args); + _autotest.addArgument("-u"); + assertEquals(args.length + 1, _autotest.getCommandLineArgs().length); + for (int i = 0; i < args.length; i++) { + assertEquals(args[i], _autotest.getCommandLineArgs()[i]); + } + assertEquals("-u", _autotest.getCommandLineArgs()[args.length]); + } + + public void testVerify() throws Exception { + TestVerifier testVerifier = new TestVerifier(); + _autotest.setVerifier(testVerifier); + assertNotNull(_autotest.getVerifier()); + assertNotNull(_autotest.getVerifier().getTargetDir()); + assertEquals(_autotest.getTargetDir(), testVerifier.getTargetDir()); + try { + _autotest.getVerifier().verify(); + fail("should have thrown"); + } catch (DriverException de) { + } + + } + + private void verifyDir(File dir, boolean ensureEmpty) { + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + if (ensureEmpty) { + assertTrue(dir.listFiles().length <= 0); + } + } + + private static class TestVerifier extends NormalVerifier { + public void verify() throws DriverException { + throw new DriverException("test verification failure"); + } + } + +} diff --git a/installer/test/java/org/python/util/install/driver/DrivableConsole.java b/installer/test/java/org/python/util/install/driver/DrivableConsole.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/DrivableConsole.java @@ -0,0 +1,82 @@ +package org.python.util.install.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.python.util.install.driver.Tunnel; + +/** + * A simple class performing console I/O, easy to test. + */ +public class DrivableConsole { + + private static final String _PROMPT = ">>>"; + private Tunnel _tunnel; + + public DrivableConsole(Tunnel tunnel) { + _tunnel = tunnel; + } + + /** + * The console logic. + */ + public void handleConsoleIO() throws Exception { + String answer; + answer = question("first question"); + if ("1".equals(answer)) { + System.out.println("answer1 is " + answer); + answer = question("second question"); + if ("2".equals(answer)) { + System.out.println("answer2 is " + answer); + answer = question("third question"); + if ("3".equals(answer)) { + System.out.println("answer3 is " + answer); + } else { + throw new Exception("wrong answer3: " + answer); + } + } else { + throw new Exception("wrong answer2: " + answer); + } + } else { + throw new Exception("wrong answer1: " + answer); + } + } + + /** + * Write a question (to normal System.out) + */ + private String question(String question) throws IOException { + question = question + " " + _PROMPT + " "; + String answer = ""; + // output to normal System.out + System.out.print(question); // intended print, not println (!) + answer = readLine(); + return answer; + } + + /** + * Send a signal through the tunnel, and then wait for the answer from the other side. + * + *
+     *     (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *     (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * 
+ */ + private String readLine() throws IOException { + InputStream inputStream; + String line = ""; + if (_tunnel == null) { + inputStream = System.in; + } else { + inputStream = _tunnel.getAnswerReceiverStream(); + _tunnel.getQuestionSenderStream().write(Tunnel.NEW_LINE.getBytes()); + _tunnel.getQuestionSenderStream().flush(); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + line = reader.readLine(); + return line; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java b/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java @@ -0,0 +1,37 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.python.util.install.driver.ConsoleDriver; +import org.python.util.install.driver.Tunnel; + +import junit.framework.TestCase; + +public class DrivableConsoleTest extends TestCase { + + private DrivableConsole _console; + private Tunnel _tunnel; + + protected void setUp() throws IOException { + _tunnel = new Tunnel(); + _console = new DrivableConsole(_tunnel); + } + + public void testDrive() throws Exception { + // sequence matters here (have to fork off the driver thread first + ConsoleDriver driver = new ConsoleDriver(_tunnel, getAnswers()); + driver.start(); + _console.handleConsoleIO(); + } + + private Collection getAnswers() { + Collection answers = new ArrayList(); + answers.add("1"); + answers.add("2"); + answers.add("3"); + return answers; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java b/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java @@ -0,0 +1,131 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.JavaVersionTester; + +public class NormalVerifierTest extends TestCase { + + private static final String DQ = "\""; + + private NormalVerifier _verifier; + + protected void setUp() throws Exception { + super.setUp(); + _verifier = new NormalVerifier(); + // use a directory containing spaces as target directory + File targetDir = createTargetDirectory(); + assertTrue(targetDir.exists()); + assertTrue(targetDir.isDirectory()); + _verifier.setTargetDir(targetDir); + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (_verifier.getTargetDir() != null) { + File autotestFile = new File(_verifier.getTargetDir().getCanonicalPath(), + NormalVerifier.AUTOTEST_PY); + if (autotestFile.exists()) { + assertTrue(autotestFile.delete()); + } + } + } + + // have to install jython first in order to activate this test + public void testVerify() throws Exception {} + + public void testGetSimpleCommand() throws Exception { + String prefix = _verifier.getTargetDir().getCanonicalPath().concat(File.separator); + String expectedCommand = prefix.concat("jython"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".bat"); + } + String expectedArgument = prefix.concat("autotest.py"); + String[] command = _verifier.getSimpleCommand(); + assertNotNull(command); + assertEquals(2, command.length); + assertEquals(expectedCommand, command[0]); + assertEquals(expectedArgument, command[1]); + } + + public void testDoShellScriptTests() { + assertTrue(_verifier.doShellScriptTests()); + } + + public void testGetShellScriptTestCommandDir() throws DriverException, IOException { + String expectedDir = _verifier.getTargetDir() + .getCanonicalPath() + .concat(File.separator) + .concat("bin"); + assertEquals(expectedDir, _verifier.getShellScriptTestCommandDir().getCanonicalPath()); + } + + public void testGetShellScriptTestContents() throws Exception { + String contents = _verifier.getShellScriptTestContents(); + // common asserts + assertNotNull(contents); + assertFalse(contents.length() == 0); + assertFalse(contents.indexOf("{0}") > 0); + assertFalse(contents.indexOf("{1}") > 0); + assertFalse(contents.indexOf("{2}") > 0); + assertFalse(contents.indexOf("{3}") > 0); + assertTrue(contents.indexOf("autotest.py") > 0); + String targetDirPath = _verifier.getTargetDir().getCanonicalPath(); + String upScriptPath = _verifier.getSimpleCommand()[1]; + String javaHome = System.getProperty(JavaVersionTester.JAVA_HOME, ""); // change this ++++++ + assertTrue(javaHome.length() > 0); + // platform specific asserts + if (Installation.isWindows()) { + assertTrue(contents.indexOf("set _INSTALL_DIR=") > 0); + assertTrue(contents.indexOf("set _INSTALL_DIR=".concat(targetDirPath)) > 0); + assertTrue(contents.indexOf("set _SCRIPT=") > 0); + assertTrue(contents.indexOf("set _SCRIPT=".concat(upScriptPath)) > 0); + assertTrue(contents.indexOf("set _JAVA_HOME=") > 0); + assertTrue(contents.indexOf("set _JAVA_HOME=".concat(javaHome)) > 0); + } else { + System.out.println(contents); + assertTrue(contents.indexOf("_INSTALL_DIR=") > 0); + assertTrue(contents.indexOf("_INSTALL_DIR=".concat(quote(targetDirPath))) > 0); + assertTrue(contents.indexOf("_SCRIPT=") > 0); + assertTrue(contents.indexOf("_SCRIPT=".concat(quote(upScriptPath))) > 0); + assertTrue(contents.indexOf("_JAVA_HOME=") > 0); + assertTrue(contents.indexOf("_JAVA_HOME=".concat(quote(javaHome))) > 0); + } + } + + public void testGetShellScriptTestCommand() throws Exception { + String prefix = _verifier.getShellScriptTestCommandDir() + .getCanonicalPath() + .concat(File.separator); + String expectedCommand = prefix.concat("jython_test"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".bat"); + } + String[] command = _verifier.getShellScriptTestCommand(); + assertNotNull(command); + assertEquals(1, command.length); + String commandFileName = command[0]; + assertEquals(expectedCommand, commandFileName); + File commandFile = new File(commandFileName); + assertTrue(commandFile.exists()); + String contents = FileHelper.readAll(commandFile); + assertNotNull(contents); + assertFalse(contents.length() == 0); + assertEquals(_verifier.getShellScriptTestContents(), contents); + } + + private File createTargetDirectory() throws IOException { + File tmpFile = File.createTempFile("NormalVerifierTest_", "with spaces"); + FileHelper.createTempDirectory(tmpFile); + return tmpFile; + } + + private String quote(String value) { + return DQ.concat(value).concat(DQ); + } +} diff --git a/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java b/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java @@ -0,0 +1,72 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.Installation; +import org.python.util.install.JarInstaller; + +import junit.framework.TestCase; + +public class StandaloneVerifierTest extends TestCase { + + private StandaloneVerifier _verifier; + + protected void setUp() throws Exception { + super.setUp(); + _verifier = new StandaloneVerifier(); + File targetDir = null; + // have to install jython first in order to activate this test + // targetDir = new File("C:/Temp/jython.autoinstall.root_54159_dir/006 + // consoleTest_54165_dir"); + _verifier.setTargetDir(targetDir); + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (_verifier.getTargetDir() != null) { + File autotestFile = new File(_verifier.getTargetDir().getCanonicalPath(), + StandaloneVerifier.AUTOTEST_PY); + if (autotestFile.exists()) { + assertTrue(autotestFile.delete()); + } + } + } + + public void testVerify() throws Exception { + if (_verifier.getTargetDir() != null) { + _verifier.verify(); + } + } + + public void testGetSimpleCommand() throws Exception { + File javaHome = new File(System.getProperty("java.home")); + assertNotNull(javaHome); + assertTrue(javaHome.exists()); + File targetDir = new File(System.getProperty(("user.dir"))); // any existing dir + assertNotNull(targetDir); + assertTrue(targetDir.exists()); + String prefix = targetDir.getCanonicalPath().concat(File.separator); + String expectedCommand = javaHome.getCanonicalPath() + .concat(File.separator) + .concat("bin") + .concat(File.separator) + .concat("java"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".exe"); + } + String expectedArgument = prefix.concat("autotest.py"); + _verifier.setTargetDir(targetDir); + String[] command = _verifier.getSimpleCommand(); + assertNotNull(command); + assertEquals(4, command.length); + assertEquals(expectedCommand, command[0]); + assertEquals("-jar", command[1]); + assertEquals(prefix.concat(JarInstaller.JYTHON_JAR), command[2]); + assertEquals(expectedArgument, command[3]); + } + + public void testDoShellScriptTests() { + // we cannot do shell script tests in standalone mode + assertFalse(_verifier.doShellScriptTests()); + } +} diff --git a/maven/build.xml b/maven/build.xml --- a/maven/build.xml +++ b/maven/build.xml @@ -67,9 +67,13 @@ + + + + - + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 02:05:53 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 9 Feb 2013 02:05:53 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?b?KTogTWVyZ2Uu?= Message-ID: <3Z2w75563qzRdk@mail.python.org> http://hg.python.org/jython/rev/59116fbad826 changeset: 7019:59116fbad826 parent: 7018:3b424500eab9 parent: 7014:a8b0a3007ed3 user: Frank Wierzbicki date: Fri Feb 08 17:04:59 2013 -0800 summary: Merge. files: Lib/test/test_threading_jy.py | 9 ++++++ NEWS | 4 ++ src/org/python/modules/_threading/Condition.java | 14 ++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_threading_jy.py b/Lib/test/test_threading_jy.py --- a/Lib/test/test_threading_jy.py +++ b/Lib/test/test_threading_jy.py @@ -43,6 +43,15 @@ def _sleep(self, n): time.sleep(random.random()) + def test_issue1988(self): + cond = threading.Condition(threading.Lock()) + locked = False + try: + locked = cond.acquire(False) + finally: + if locked: + cond.release() + class TwistedTestCase(unittest.TestCase): diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -35,6 +35,10 @@ Bugs Fixed - [ 1880 ] Sha 224 library not present in Jython +Jython 2.5.4rc2 + Bugs Fixed + - [ 1988 ] API for threading.condition fails to accept *args for acquire + Jython 2.5.4rc1 Bugs Fixed - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. diff --git a/src/org/python/modules/_threading/Condition.java b/src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java +++ b/src/org/python/modules/_threading/Condition.java @@ -38,12 +38,16 @@ } public boolean acquire() { - return Condition_acquire(); + return Condition_acquire(true); } - @ExposedMethod - final boolean Condition_acquire() { - return _lock.acquire(); + public boolean acquire(boolean blocking) { + return Condition_acquire(blocking); + } + + @ExposedMethod(defaults = "true") + final boolean Condition_acquire(boolean blocking) { + return _lock.acquire(blocking); } public PyObject __enter__(ThreadState ts) { @@ -53,7 +57,7 @@ @ExposedMethod final PyObject Condition___enter__() { - Condition_acquire(); + Condition_acquire(true); return this; } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 02:05:55 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 9 Feb 2013 02:05:55 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uIChtZXJnZSAyLjUgLT4gMi41KTog?= =?utf-8?q?Merge=2E?= Message-ID: <3Z2w770XJXzSbc@mail.python.org> http://hg.python.org/jython/rev/82e53a609c9b changeset: 7020:82e53a609c9b branch: 2.5 parent: 7017:041760b4c0d7 parent: 7013:f9c6dc60c4eb user: Frank Wierzbicki date: Fri Feb 08 17:05:16 2013 -0800 summary: Merge. files: Lib/test/test_threading_jy.py | 9 ++++++ NEWS | 4 ++ src/org/python/modules/_threading/Condition.java | 14 ++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_threading_jy.py b/Lib/test/test_threading_jy.py --- a/Lib/test/test_threading_jy.py +++ b/Lib/test/test_threading_jy.py @@ -43,6 +43,15 @@ def _sleep(self, n): time.sleep(random.random()) + def test_issue1988(self): + cond = threading.Condition(threading.Lock()) + locked = False + try: + locked = cond.acquire(False) + finally: + if locked: + cond.release() + class TwistedTestCase(unittest.TestCase): diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -3,6 +3,10 @@ Jython 2.7a1 Bugs Fixed +Jython 2.5.4rc2 + Bugs Fixed + - [ 1988 ] API for threading.condition fails to accept *args for acquire + Jython 2.5.4rc1 Bugs Fixed - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. diff --git a/src/org/python/modules/_threading/Condition.java b/src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java +++ b/src/org/python/modules/_threading/Condition.java @@ -38,12 +38,16 @@ } public boolean acquire() { - return Condition_acquire(); + return Condition_acquire(true); } - @ExposedMethod - final boolean Condition_acquire() { - return _lock.acquire(); + public boolean acquire(boolean blocking) { + return Condition_acquire(blocking); + } + + @ExposedMethod(defaults = "true") + final boolean Condition_acquire(boolean blocking) { + return _lock.acquire(blocking); } public PyObject __enter__(ThreadState ts) { @@ -53,7 +57,7 @@ @ExposedMethod final PyObject Condition___enter__() { - Condition_acquire(); + Condition_acquire(true); return this; } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 16:51:38 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 9 Feb 2013 16:51:38 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiBGaXggZm9yICMxOTcx?= =?utf-8?q?=3A_platform=2Epy_-_=27NoneType=27_object_has_no_attribute_=27g?= =?utf-8?q?roups=27?= Message-ID: <3Z3Hn654l3zR7M@mail.python.org> http://hg.python.org/jython/rev/56e560cd6209 changeset: 7021:56e560cd6209 branch: 2.5 user: Alan Kennedy date: Sat Feb 09 15:49:21 2013 +0000 summary: Fix for #1971: platform.py - 'NoneType' object has no attribute 'groups' files: Lib/platform.py | 3 ++- build.xml | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py --- a/Lib/platform.py +++ b/Lib/platform.py @@ -1111,8 +1111,9 @@ ### Various APIs for extracting information from sys.version +# buildno can be an empty string if the jar was compiled without mercurial present _sys_version_parser = re.compile(r'([\w.+]+)\s*' - '\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*' + '\(#?([^,]+)?,\s*([\w ]+),\s*([\w :]+)\)\s*' '\[([^\]]+)\]?') _sys_version_cache = None diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -342,8 +342,10 @@ - - + + + + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 20:30:06 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 9 Feb 2013 20:30:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Allow_=5Fio=2EFileIO_to_wra?= =?utf-8?q?p_more_streams?= Message-ID: <3Z3NdB3cfWzSPt@mail.python.org> http://hg.python.org/jython/rev/e5ab567a191b changeset: 7022:e5ab567a191b parent: 6961:98b9c3a9076e user: Jeff Allen date: Sat Feb 09 18:55:49 2013 +0000 summary: Allow _io.FileIO to wrap more streams The fileno() return from PyFile would sometimes be a StreamIO, notably in the case of the streams sys.(stdin|stdout|stderr), which are now acceptable to _io.open and _io.FileIO. Changes close semantics slightly (hence change to test_io). files: Lib/test/test_io.py | 4 +- src/org/python/modules/_io/PyFileIO.java | 69 ++-- tests/java/org/python/modules/_io/_ioTest.java | 120 ++++++++- 3 files changed, 134 insertions(+), 59 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2552,8 +2552,8 @@ self.assertEqual(g.raw.mode, "wb") self.assertEqual(g.name, f.fileno()) self.assertEqual(g.raw.name, f.fileno()) - f.close() - g.close() + g.close() # Jython difference: close g first (which may flush) ... + f.close() # Jython difference: then close f, which closes the fd def test_io_after_close(self): for kwargs in [ diff --git a/src/org/python/modules/_io/PyFileIO.java b/src/org/python/modules/_io/PyFileIO.java --- a/src/org/python/modules/_io/PyFileIO.java +++ b/src/org/python/modules/_io/PyFileIO.java @@ -1,15 +1,10 @@ -/* Copyright (c)2012 Jython Developers */ +/* Copyright (c)2013 Jython Developers */ package org.python.modules._io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.nio.channels.Channel; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; import org.python.core.ArgParser; import org.python.core.BuiltinDocs; @@ -26,6 +21,7 @@ import org.python.core.PyUnicode; import org.python.core.io.FileIO; import org.python.core.io.RawIOBase; +import org.python.core.io.StreamIO; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; @@ -39,8 +35,8 @@ public static final PyType TYPE = PyType.fromClass(PyFileIO.class); - /** The FileIO to which we delegate operations not complete locally. */ - private FileIO ioDelegate; + /** The {@link FileIO} or {@link StreamIO} to which we delegate operations not complete locally. */ + private RawIOBase ioDelegate; /* * Implementation note: CPython fileio does not use the base-class, possibly overridden, @@ -105,12 +101,15 @@ */ public PyFileIO(PyType subtype, PyObject file, OpenMode mode, boolean closefd) { super(subtype); - setDelegate(file, mode, closefd); - this.closefd = closefd; + // Establish the direction(s) of flow readable = mode.reading | mode.updating; writable = mode.writing | mode.updating | mode.appending; + // Assign a delegate according to the file argument + this.closefd = closefd; + setDelegate(file, mode); + // The mode string of a raw file always asserts it is binary: "rb", "rb+", or "wb". if (readable) { this.mode = new PyString(writable ? "rb+" : "rb"); @@ -132,9 +131,8 @@ * * @param file name or descriptor * @param mode parsed file open mode - * @param closefd must be true if file is in fact a name (checked, not used) */ - private void setDelegate(PyObject file, OpenMode mode, boolean closefd) { + private void setDelegate(PyObject file, OpenMode mode) { if (file instanceof PyString) { // Open a file by name @@ -151,23 +149,13 @@ */ Object fd = file.__tojava__(Object.class); - if (fd instanceof RawIOBase) { - // It is the "Jython file descriptor" from which we can get a channel. + if (fd instanceof FileIO || fd instanceof StreamIO) { /* - * If the argument were a FileIO, could we assign it directly to ioDelegate? I think - * not: there would be a problem with close and closefd=False since it is not the - * PyFileIO that keeps state. + * It is the "Jython file descriptor", of a type suitable to be the ioDelegate. The + * allowed types are able to give us a non-null InputStream or OutputStream, + * according to direction. */ - Channel channel = ((RawIOBase)fd).getChannel(); - if (channel instanceof FileChannel) { - if (channel.isOpen()) { - FileChannel fc = (FileChannel)channel; - ioDelegate = new FileIO(fc, mode.forFileIO()); - } else { - // File not open (we have to check as FileIO doesn't) - throw Py.OSError(Errno.EBADF); - } - } + ioDelegate = (RawIOBase)fd; } } @@ -175,11 +163,22 @@ if (ioDelegate == null) { // The file was a type we don't know how to use throw Py.TypeError(String.format("invalid file: %s", file.__repr__().asString())); + + } else { + + if (ioDelegate.closed()) { + // A closed file descriptor is a "bad descriptor" + throw Py.OSError(Errno.EBADF); + } + + if ((readable && !ioDelegate.readable()) || (writable && !ioDelegate.writable())) { + // Requested mode in conflict with underlying file or stream + throw tailoredValueError(readable ? "read" : "writ"); + } + + // The name is either the textual name or a file descriptor (see Python docs) + fastGetDict().__setitem__("name", file); } - - // The name is either the textual name or a file descriptor (see Python docs) - fastGetDict().__setitem__("name", file); - } private static final String[] openArgs = {"file", "mode", "closefd"}; @@ -239,8 +238,8 @@ PyArray a = (PyArray)buf; try { - ReadableByteChannel ch = ioDelegate.getChannel(); - InputStream is = Channels.newInputStream(ch); + // The ioDelegate, if readable, can always provide an InputStream (see setDelegate) + InputStream is = ioDelegate.asInputStream(); count = a.fillFromStream(is); count *= a.getItemsize(); } catch (IOException ioe) { @@ -281,8 +280,8 @@ if (buf instanceof PyArray) { // Special case: PyArray knows how to write itself try { - WritableByteChannel ch = ioDelegate.getChannel(); - OutputStream os = Channels.newOutputStream(ch); + // The ioDelegate, if writable, can always provide an OutputStream (see setDelegate) + OutputStream os = ioDelegate.asOutputStream(); count = ((PyArray)buf).toStream(os); } catch (IOException ioe) { throw Py.IOError(ioe); diff --git a/tests/java/org/python/modules/_io/_ioTest.java b/tests/java/org/python/modules/_io/_ioTest.java --- a/tests/java/org/python/modules/_io/_ioTest.java +++ b/tests/java/org/python/modules/_io/_ioTest.java @@ -5,9 +5,15 @@ import static org.junit.matchers.JUnitMatchers.*; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; -import java.io.IOError; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; import java.util.Arrays; import org.junit.Before; @@ -18,6 +24,7 @@ import org.python.core.PyObject; import org.python.core.PyStringMap; import org.python.core.PySystemState; +import org.python.core.imp; import org.python.core.io.RawIOBase; import org.python.util.PythonInterpreter; @@ -28,7 +35,12 @@ */ public class _ioTest { - /** We need the interpreter to be initialised for these tests **/ + // Some file names to use + private final String FILE1 = "$test_1_tmp"; + private final String FILE2 = "$test_2_tmp"; + private final String FILE3 = "$test_3_tmp"; + + // We need the interpreter to be initialised for these tests. PySystemState systemState; PyStringMap dict; PythonInterpreter interp; @@ -94,6 +106,61 @@ } } + /** Check PyFile().fileno() is acceptable to _io.open() */ + @Test + public void openPyFileByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + PyFile file = new PyFile(FILE1, "w", 1); + openByFilenoTest(file, "wb"); + } + + /** Check PyFile(OutputStream).fileno() is acceptable to _io.open() */ + @Test + public void openPyFileOStreamByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + OutputStream ostream = new FileOutputStream(FILE1); + PyFile file = new PyFile(ostream); + openByFilenoTest(file, "wb"); + } + + /** Check sys.stdin.fileno() is acceptable to _io.open() */ + @Test + public void openStdinByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stdin, "rb"); + } + + /** Check sys.stdout.fileno() is acceptable to _io.open() */ + @Test + public void openStdoutByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stdout, "wb"); + } + + /** Check sys.stderr.fileno() is acceptable to _io.open() */ + @Test + public void openStderrByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stderr, "wb"); + } + + /** + * Test opening by a "file descriptor" obtained from another file or stream. The main purpose is + * to test that the return from fileno() is acceptable to _io.open(). + * + * @param file anything with a "fileno" function + * @param mode mode string "rb" etc. + * @throws IOException + */ + public void openByFilenoTest(PyObject file, String mode) throws IOException { + PyObject pyfd = file.invoke("fileno"); + RawIOBase fd = (RawIOBase)pyfd.__tojava__(RawIOBase.class); + PyObject[] args = new PyObject[] {pyfd, Py.newString(mode), Py.False}; + String[] kwds = {"closefd"}; + PyObject file2 = _io.open(args, kwds); + file2.invoke("close"); + } + /** * Test automatic closing of files when the interpreter finally exits. Done correctly, text * written to any kind of file-like object should be flushed to disk and the file closed when @@ -106,9 +173,9 @@ public void closeNeglectedFiles() throws IOException { // File names - final String F = "$test_1_tmp"; // Raw file - final String FB = "$test_2_tmp"; // Buffered file - final String FT = "$test_3_tmp"; // Test file + final String F = FILE1; // Raw file + final String FB = FILE2; // Buffered file + final String FT = FILE3; // Test file String expText = "Line 1\nLine 2\nLine 3."; // Note: all ascii, but with new lines byte[] expBytes = expText.getBytes(); @@ -149,11 +216,11 @@ assertTrue(pyft.__closed); // If they were not closed properly not all bytes will reach the files. - checkFileContent(F, expBytes); - checkFileContent(FB, expBytes); + checkFileContent(F, expBytes, true); + checkFileContent(FB, expBytes, true); // Expect that TextIOWrapper should have adjusted the line separator - checkFileContent(FT, newlineFix(expText)); + checkFileContent(FT, newlineFix(expText), true); } /** @@ -165,9 +232,10 @@ @Test public void closeNeglectedPyFiles() throws IOException { - final String F = "$test_1_tmp"; // Raw file - final String FB = "$test_2_tmp"; // Buffered file - final String FT = "$test_3_tmp"; // Test file + // File names + final String F = FILE1; // Raw file + final String FB = FILE2; // Buffered file + final String FT = FILE3; // Test file String expText = "Line 1\nLine 2\nLine 3."; byte[] expBytes = expText.getBytes(); @@ -183,19 +251,19 @@ interp.exec("f = open('" + F + "', 'wb', 0)"); PyFile pyf = (PyFile)interp.get("f"); assertNotNull(pyf); - RawIOBase r = (RawIOBase) pyf.fileno().__tojava__(RawIOBase.class); + RawIOBase r = (RawIOBase)pyf.fileno().__tojava__(RawIOBase.class); // This should get us a buffered binary PyFile called fb interp.exec("fb = open('" + FB + "', 'wb')"); PyFile pyfb = (PyFile)interp.get("fb"); assertNotNull(pyfb); - RawIOBase rb = (RawIOBase) pyfb.fileno().__tojava__(RawIOBase.class); + RawIOBase rb = (RawIOBase)pyfb.fileno().__tojava__(RawIOBase.class); // This should get us an buffered text PyFile called ft interp.exec("ft = open('" + FT + "', 'w')"); PyFile pyft = (PyFile)interp.get("ft"); assertNotNull(pyft); - RawIOBase rt = (RawIOBase) pyft.fileno().__tojava__(RawIOBase.class); + RawIOBase rt = (RawIOBase)pyft.fileno().__tojava__(RawIOBase.class); // Write the bytes test material to each file but don't close it interp.exec("f.write(b)"); @@ -211,39 +279,47 @@ assertTrue(rt.closed()); // If they were not closed properly not all bytes will reach the files. - checkFileContent(F, expBytes); - checkFileContent(FB, expBytes); + checkFileContent(F, expBytes, true); + checkFileContent(FB, expBytes, true); // Expect that TextIOWrapper should have adjusted the line separator - checkFileContent(FT, newlineFix(expText)); + checkFileContent(FT, newlineFix(expText), true); } /** - * Check the file contains the bytes we expect and delete the file. If it was not closed - * properly (layers in the right order and a flush) not all bytes will have reached the files. + * Check the file contains the bytes we expect and optionally delete the file. If it was + * not closed properly (layers in the right order and a flush) not all bytes will have reached + * the files. * * @param name of file * @param expBytes expected + * @param delete the file if true * @throws IOException if cannot open/read */ - private static void checkFileContent(String name, byte[] expBytes) throws IOException { + private static void checkFileContent(String name, byte[] expBytes, boolean delete) + throws IOException { + // Open and read byte[] r = new byte[2 * expBytes.length]; File f = new File(name); FileInputStream in = new FileInputStream(f); int n = in.read(r); in.close(); + // Check as expected String msg = "Bytes read from " + name; assertEquals(msg, expBytes.length, n); byte[] resBytes = Arrays.copyOf(r, n); assertArrayEquals(msg, expBytes, resBytes); - f.delete(); + // Delete the file + if (delete) { + f.delete(); + } } - /** * Replace "\n" characters by the system-defined newline sequence and return as bytes. + * * @param expText to translate * @return result as bytes */ -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 20:30:08 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 9 Feb 2013 20:30:08 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge?= Message-ID: <3Z3NdD1bPlzSQK@mail.python.org> http://hg.python.org/jython/rev/b6819455da6b changeset: 7023:b6819455da6b parent: 7019:59116fbad826 parent: 7022:e5ab567a191b user: Jeff Allen date: Sat Feb 09 19:18:06 2013 +0000 summary: Merge files: Lib/test/test_io.py | 4 +- src/org/python/modules/_io/PyFileIO.java | 69 ++-- tests/java/org/python/modules/_io/_ioTest.java | 120 ++++++++- 3 files changed, 134 insertions(+), 59 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2552,8 +2552,8 @@ self.assertEqual(g.raw.mode, "wb") self.assertEqual(g.name, f.fileno()) self.assertEqual(g.raw.name, f.fileno()) - f.close() - g.close() + g.close() # Jython difference: close g first (which may flush) ... + f.close() # Jython difference: then close f, which closes the fd def test_io_after_close(self): for kwargs in [ diff --git a/src/org/python/modules/_io/PyFileIO.java b/src/org/python/modules/_io/PyFileIO.java --- a/src/org/python/modules/_io/PyFileIO.java +++ b/src/org/python/modules/_io/PyFileIO.java @@ -1,15 +1,10 @@ -/* Copyright (c)2012 Jython Developers */ +/* Copyright (c)2013 Jython Developers */ package org.python.modules._io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.nio.channels.Channel; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; import org.python.core.ArgParser; import org.python.core.BuiltinDocs; @@ -26,6 +21,7 @@ import org.python.core.PyUnicode; import org.python.core.io.FileIO; import org.python.core.io.RawIOBase; +import org.python.core.io.StreamIO; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; @@ -39,8 +35,8 @@ public static final PyType TYPE = PyType.fromClass(PyFileIO.class); - /** The FileIO to which we delegate operations not complete locally. */ - private FileIO ioDelegate; + /** The {@link FileIO} or {@link StreamIO} to which we delegate operations not complete locally. */ + private RawIOBase ioDelegate; /* * Implementation note: CPython fileio does not use the base-class, possibly overridden, @@ -105,12 +101,15 @@ */ public PyFileIO(PyType subtype, PyObject file, OpenMode mode, boolean closefd) { super(subtype); - setDelegate(file, mode, closefd); - this.closefd = closefd; + // Establish the direction(s) of flow readable = mode.reading | mode.updating; writable = mode.writing | mode.updating | mode.appending; + // Assign a delegate according to the file argument + this.closefd = closefd; + setDelegate(file, mode); + // The mode string of a raw file always asserts it is binary: "rb", "rb+", or "wb". if (readable) { this.mode = new PyString(writable ? "rb+" : "rb"); @@ -132,9 +131,8 @@ * * @param file name or descriptor * @param mode parsed file open mode - * @param closefd must be true if file is in fact a name (checked, not used) */ - private void setDelegate(PyObject file, OpenMode mode, boolean closefd) { + private void setDelegate(PyObject file, OpenMode mode) { if (file instanceof PyString) { // Open a file by name @@ -151,23 +149,13 @@ */ Object fd = file.__tojava__(Object.class); - if (fd instanceof RawIOBase) { - // It is the "Jython file descriptor" from which we can get a channel. + if (fd instanceof FileIO || fd instanceof StreamIO) { /* - * If the argument were a FileIO, could we assign it directly to ioDelegate? I think - * not: there would be a problem with close and closefd=False since it is not the - * PyFileIO that keeps state. + * It is the "Jython file descriptor", of a type suitable to be the ioDelegate. The + * allowed types are able to give us a non-null InputStream or OutputStream, + * according to direction. */ - Channel channel = ((RawIOBase)fd).getChannel(); - if (channel instanceof FileChannel) { - if (channel.isOpen()) { - FileChannel fc = (FileChannel)channel; - ioDelegate = new FileIO(fc, mode.forFileIO()); - } else { - // File not open (we have to check as FileIO doesn't) - throw Py.OSError(Errno.EBADF); - } - } + ioDelegate = (RawIOBase)fd; } } @@ -175,11 +163,22 @@ if (ioDelegate == null) { // The file was a type we don't know how to use throw Py.TypeError(String.format("invalid file: %s", file.__repr__().asString())); + + } else { + + if (ioDelegate.closed()) { + // A closed file descriptor is a "bad descriptor" + throw Py.OSError(Errno.EBADF); + } + + if ((readable && !ioDelegate.readable()) || (writable && !ioDelegate.writable())) { + // Requested mode in conflict with underlying file or stream + throw tailoredValueError(readable ? "read" : "writ"); + } + + // The name is either the textual name or a file descriptor (see Python docs) + fastGetDict().__setitem__("name", file); } - - // The name is either the textual name or a file descriptor (see Python docs) - fastGetDict().__setitem__("name", file); - } private static final String[] openArgs = {"file", "mode", "closefd"}; @@ -239,8 +238,8 @@ PyArray a = (PyArray)buf; try { - ReadableByteChannel ch = ioDelegate.getChannel(); - InputStream is = Channels.newInputStream(ch); + // The ioDelegate, if readable, can always provide an InputStream (see setDelegate) + InputStream is = ioDelegate.asInputStream(); count = a.fillFromStream(is); count *= a.getItemsize(); } catch (IOException ioe) { @@ -281,8 +280,8 @@ if (buf instanceof PyArray) { // Special case: PyArray knows how to write itself try { - WritableByteChannel ch = ioDelegate.getChannel(); - OutputStream os = Channels.newOutputStream(ch); + // The ioDelegate, if writable, can always provide an OutputStream (see setDelegate) + OutputStream os = ioDelegate.asOutputStream(); count = ((PyArray)buf).toStream(os); } catch (IOException ioe) { throw Py.IOError(ioe); diff --git a/tests/java/org/python/modules/_io/_ioTest.java b/tests/java/org/python/modules/_io/_ioTest.java --- a/tests/java/org/python/modules/_io/_ioTest.java +++ b/tests/java/org/python/modules/_io/_ioTest.java @@ -5,9 +5,15 @@ import static org.junit.matchers.JUnitMatchers.*; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; -import java.io.IOError; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; import java.util.Arrays; import org.junit.Before; @@ -18,6 +24,7 @@ import org.python.core.PyObject; import org.python.core.PyStringMap; import org.python.core.PySystemState; +import org.python.core.imp; import org.python.core.io.RawIOBase; import org.python.util.PythonInterpreter; @@ -28,7 +35,12 @@ */ public class _ioTest { - /** We need the interpreter to be initialised for these tests **/ + // Some file names to use + private final String FILE1 = "$test_1_tmp"; + private final String FILE2 = "$test_2_tmp"; + private final String FILE3 = "$test_3_tmp"; + + // We need the interpreter to be initialised for these tests. PySystemState systemState; PyStringMap dict; PythonInterpreter interp; @@ -94,6 +106,61 @@ } } + /** Check PyFile().fileno() is acceptable to _io.open() */ + @Test + public void openPyFileByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + PyFile file = new PyFile(FILE1, "w", 1); + openByFilenoTest(file, "wb"); + } + + /** Check PyFile(OutputStream).fileno() is acceptable to _io.open() */ + @Test + public void openPyFileOStreamByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + OutputStream ostream = new FileOutputStream(FILE1); + PyFile file = new PyFile(ostream); + openByFilenoTest(file, "wb"); + } + + /** Check sys.stdin.fileno() is acceptable to _io.open() */ + @Test + public void openStdinByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stdin, "rb"); + } + + /** Check sys.stdout.fileno() is acceptable to _io.open() */ + @Test + public void openStdoutByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stdout, "wb"); + } + + /** Check sys.stderr.fileno() is acceptable to _io.open() */ + @Test + public void openStderrByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stderr, "wb"); + } + + /** + * Test opening by a "file descriptor" obtained from another file or stream. The main purpose is + * to test that the return from fileno() is acceptable to _io.open(). + * + * @param file anything with a "fileno" function + * @param mode mode string "rb" etc. + * @throws IOException + */ + public void openByFilenoTest(PyObject file, String mode) throws IOException { + PyObject pyfd = file.invoke("fileno"); + RawIOBase fd = (RawIOBase)pyfd.__tojava__(RawIOBase.class); + PyObject[] args = new PyObject[] {pyfd, Py.newString(mode), Py.False}; + String[] kwds = {"closefd"}; + PyObject file2 = _io.open(args, kwds); + file2.invoke("close"); + } + /** * Test automatic closing of files when the interpreter finally exits. Done correctly, text * written to any kind of file-like object should be flushed to disk and the file closed when @@ -106,9 +173,9 @@ public void closeNeglectedFiles() throws IOException { // File names - final String F = "$test_1_tmp"; // Raw file - final String FB = "$test_2_tmp"; // Buffered file - final String FT = "$test_3_tmp"; // Test file + final String F = FILE1; // Raw file + final String FB = FILE2; // Buffered file + final String FT = FILE3; // Test file String expText = "Line 1\nLine 2\nLine 3."; // Note: all ascii, but with new lines byte[] expBytes = expText.getBytes(); @@ -149,11 +216,11 @@ assertTrue(pyft.__closed); // If they were not closed properly not all bytes will reach the files. - checkFileContent(F, expBytes); - checkFileContent(FB, expBytes); + checkFileContent(F, expBytes, true); + checkFileContent(FB, expBytes, true); // Expect that TextIOWrapper should have adjusted the line separator - checkFileContent(FT, newlineFix(expText)); + checkFileContent(FT, newlineFix(expText), true); } /** @@ -165,9 +232,10 @@ @Test public void closeNeglectedPyFiles() throws IOException { - final String F = "$test_1_tmp"; // Raw file - final String FB = "$test_2_tmp"; // Buffered file - final String FT = "$test_3_tmp"; // Test file + // File names + final String F = FILE1; // Raw file + final String FB = FILE2; // Buffered file + final String FT = FILE3; // Test file String expText = "Line 1\nLine 2\nLine 3."; byte[] expBytes = expText.getBytes(); @@ -183,19 +251,19 @@ interp.exec("f = open('" + F + "', 'wb', 0)"); PyFile pyf = (PyFile)interp.get("f"); assertNotNull(pyf); - RawIOBase r = (RawIOBase) pyf.fileno().__tojava__(RawIOBase.class); + RawIOBase r = (RawIOBase)pyf.fileno().__tojava__(RawIOBase.class); // This should get us a buffered binary PyFile called fb interp.exec("fb = open('" + FB + "', 'wb')"); PyFile pyfb = (PyFile)interp.get("fb"); assertNotNull(pyfb); - RawIOBase rb = (RawIOBase) pyfb.fileno().__tojava__(RawIOBase.class); + RawIOBase rb = (RawIOBase)pyfb.fileno().__tojava__(RawIOBase.class); // This should get us an buffered text PyFile called ft interp.exec("ft = open('" + FT + "', 'w')"); PyFile pyft = (PyFile)interp.get("ft"); assertNotNull(pyft); - RawIOBase rt = (RawIOBase) pyft.fileno().__tojava__(RawIOBase.class); + RawIOBase rt = (RawIOBase)pyft.fileno().__tojava__(RawIOBase.class); // Write the bytes test material to each file but don't close it interp.exec("f.write(b)"); @@ -211,39 +279,47 @@ assertTrue(rt.closed()); // If they were not closed properly not all bytes will reach the files. - checkFileContent(F, expBytes); - checkFileContent(FB, expBytes); + checkFileContent(F, expBytes, true); + checkFileContent(FB, expBytes, true); // Expect that TextIOWrapper should have adjusted the line separator - checkFileContent(FT, newlineFix(expText)); + checkFileContent(FT, newlineFix(expText), true); } /** - * Check the file contains the bytes we expect and delete the file. If it was not closed - * properly (layers in the right order and a flush) not all bytes will have reached the files. + * Check the file contains the bytes we expect and optionally delete the file. If it was + * not closed properly (layers in the right order and a flush) not all bytes will have reached + * the files. * * @param name of file * @param expBytes expected + * @param delete the file if true * @throws IOException if cannot open/read */ - private static void checkFileContent(String name, byte[] expBytes) throws IOException { + private static void checkFileContent(String name, byte[] expBytes, boolean delete) + throws IOException { + // Open and read byte[] r = new byte[2 * expBytes.length]; File f = new File(name); FileInputStream in = new FileInputStream(f); int n = in.read(r); in.close(); + // Check as expected String msg = "Bytes read from " + name; assertEquals(msg, expBytes.length, n); byte[] resBytes = Arrays.copyOf(r, n); assertArrayEquals(msg, expBytes, resBytes); - f.delete(); + // Delete the file + if (delete) { + f.delete(); + } } - /** * Replace "\n" characters by the system-defined newline sequence and return as bytes. + * * @param expText to translate * @return result as bytes */ -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 9 21:04:49 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 9 Feb 2013 21:04:49 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fixing_handling_of_buildno_?= =?utf-8?q?on_windows?= Message-ID: <3Z3PPF3hG3zSbk@mail.python.org> http://hg.python.org/jython/rev/d5a22e9b622a changeset: 7024:d5a22e9b622a user: Alan Kennedy date: Sat Feb 09 20:03:19 2013 +0000 summary: Fixing handling of buildno on windows files: build.xml | 6 ++++-- 1 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -359,8 +359,10 @@ - - + + + + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 00:08:19 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sun, 10 Feb 2013 00:08:19 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Prepare_for_2=2E7b1=2E?= Message-ID: <3Z3TSz5JjMzRNW@mail.python.org> http://hg.python.org/jython/rev/53ae75d23e28 changeset: 7025:53ae75d23e28 user: Frank Wierzbicki date: Sat Feb 09 15:08:05 2013 -0800 summary: Prepare for 2.7b1. files: README.txt | 13 +++++++++++-- build.xml | 7 ++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.txt b/README.txt --- a/README.txt +++ b/README.txt @@ -3,9 +3,18 @@ This is the first beta release of the 2.7 version of Jython. Thanks to Adconion Media Group (http://www.adconion.com/) for sponsoring this release. -Thanks to all who contributed to Jython. +Thanks to all who contribute to Jython. -This release TODO +Jython 2.7b1 brings us up to language level compatibility with the 2.7 version +of CPython. We have focused largely on CPython compatibility, and so this +release of Jython can run more pure Python apps then any previous release. +Please see the NEWS file for detailed release notes. Some notable new features +in this release are: a bytearray implementation, a buffer api, memoryview, a +bz2 module. + +As the first beta release, this marks a change in the focus of development +from adding features to bug fixing and on getting more of the test suite +running properly. Please see the NEWS file for detailed release notes. diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -92,15 +92,20 @@ - + + + + + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 00:09:58 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sun, 10 Feb 2013 00:09:58 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?b?OiBNZXJnZSAyLjUu?= Message-ID: <3Z3TVt10V6zRXP@mail.python.org> http://hg.python.org/jython/rev/8a556c4cc281 changeset: 7026:8a556c4cc281 parent: 7025:53ae75d23e28 parent: 7021:56e560cd6209 user: Frank Wierzbicki date: Sat Feb 09 15:09:36 2013 -0800 summary: Merge 2.5. files: NEWS | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -39,6 +39,10 @@ Bugs Fixed - [ 1988 ] API for threading.condition fails to accept *args for acquire +Jython 2.5.4rc2 + Bugs Fixed + - [ 1988 ] API for threading.condition fails to accept *args for acquire + Jython 2.5.4rc1 Bugs Fixed - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 00:10:39 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sun, 10 Feb 2013 00:10:39 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Added_tag_v2=2E7b1_for_chan?= =?utf-8?q?geset_8a556c4cc281?= Message-ID: <3Z3TWg31CwzSZ9@mail.python.org> http://hg.python.org/jython/rev/ac42d59644e9 changeset: 7027:ac42d59644e9 user: Frank Wierzbicki date: Sat Feb 09 15:10:24 2013 -0800 summary: Added tag v2.7b1 for changeset 8a556c4cc281 files: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -64,3 +64,4 @@ ac5609677c1382f14d0e04178fe6dd4108e4c231 v2.7.0a2 3d2dbae23c5292b7f31ac4c92fa6d1afd8bd8eb2 v2.5.3 f5b12dc4ff970c9594c99fc895772958c4a669a0 v2.5.4rc1 +8a556c4cc2810912e4ef277d53668ffc9d9f5772 v2.7b1 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 12:50:16 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 10 Feb 2013 12:50:16 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231753=3A_zlib_doesn=27t_c?= =?utf-8?q?all_end=28=29_on_compress_and_decompress=2E_Thanks_to_Kelly?= Message-ID: <3Z3pN854sBzSQw@mail.python.org> http://hg.python.org/jython/rev/d39b428d5c99 changeset: 7028:d39b428d5c99 user: Alan Kennedy date: Sun Feb 10 11:46:10 2013 +0000 summary: #1753: zlib doesn't call end() on compress and decompress. Thanks to Kelly Campbell for the patch files: Lib/zlib.py | 19 ++++++++++++------- 1 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Lib/zlib.py b/Lib/zlib.py --- a/Lib/zlib.py +++ b/Lib/zlib.py @@ -61,16 +61,21 @@ if level < Z_BEST_SPEED or level > Z_BEST_COMPRESSION: raise error, "Bad compression level" deflater = Deflater(level, 0) - string = _to_input(string) - deflater.setInput(string, 0, len(string)) - deflater.finish() - return _get_deflate_data(deflater) + try: + string = _to_input(string) + deflater.setInput(string, 0, len(string)) + deflater.finish() + return _get_deflate_data(deflater) + finally: + deflater.end() def decompress(string, wbits=0, bufsize=16384): inflater = Inflater(wbits < 0) - inflater.setInput(_to_input(string)) - - return _get_inflate_data(inflater) + try: + inflater.setInput(_to_input(string)) + return _get_inflate_data(inflater) + finally: + inflater.end() class compressobj: # all jython uses wbits for is deciding whether to skip the header if it's negative -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 12:50:18 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 10 Feb 2013 12:50:18 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_Updated_NEWS?= Message-ID: <3Z3pNB0SBgzQgc@mail.python.org> http://hg.python.org/jython/rev/16239903080d changeset: 7029:16239903080d branch: 2.5 parent: 7021:56e560cd6209 user: Alan Kennedy date: Sat Feb 09 15:57:11 2013 +0000 summary: Updated NEWS files: NEWS | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Jython 2.5.4rc2 Bugs Fixed + - [ 1971 ] platform.py - 'NoneType' object has no attribute 'groups' - [ 1988 ] API for threading.condition fails to accept *args for acquire Jython 2.5.4rc1 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 12:50:19 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 10 Feb 2013 12:50:19 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiAjMTc1MzogemxpYiBk?= =?utf-8?q?oesn=27t_call_end=28=29_on_compress_and_decompress=2E_Thanks_to?= =?utf-8?q?_Kelly?= Message-ID: <3Z3pNC2swFzSQw@mail.python.org> http://hg.python.org/jython/rev/fa5bcc1c80cb changeset: 7030:fa5bcc1c80cb branch: 2.5 user: Alan Kennedy date: Sun Feb 10 11:44:14 2013 +0000 summary: #1753: zlib doesn't call end() on compress and decompress. Thanks to Kelly Campbell for the patch files: Lib/zlib.py | 19 ++++++++++++------- 1 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Lib/zlib.py b/Lib/zlib.py --- a/Lib/zlib.py +++ b/Lib/zlib.py @@ -61,16 +61,21 @@ if level < Z_BEST_SPEED or level > Z_BEST_COMPRESSION: raise error, "Bad compression level" deflater = Deflater(level, 0) - string = _to_input(string) - deflater.setInput(string, 0, len(string)) - deflater.finish() - return _get_deflate_data(deflater) + try: + string = _to_input(string) + deflater.setInput(string, 0, len(string)) + deflater.finish() + return _get_deflate_data(deflater) + finally: + deflater.end() def decompress(string, wbits=0, bufsize=16384): inflater = Inflater(wbits < 0) - inflater.setInput(_to_input(string)) - - return _get_inflate_data(inflater) + try: + inflater.setInput(_to_input(string)) + return _get_inflate_data(inflater) + finally: + inflater.end() class compressobj: # all jython uses wbits for is deciding whether to skip the header if it's negative -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 12:50:20 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 10 Feb 2013 12:50:20 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?q?=3A_merge_w/2=2E5?= Message-ID: <3Z3pND5PtNzSSD@mail.python.org> http://hg.python.org/jython/rev/cbbc0d996165 changeset: 7031:cbbc0d996165 parent: 7028:d39b428d5c99 parent: 7030:fa5bcc1c80cb user: Alan Kennedy date: Sun Feb 10 11:48:28 2013 +0000 summary: merge w/2.5 files: NEWS | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -41,6 +41,7 @@ Jython 2.5.4rc2 Bugs Fixed + - [ 1971 ] platform.py - 'NoneType' object has no attribute 'groups' - [ 1988 ] API for threading.condition fails to accept *args for acquire Jython 2.5.4rc1 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 15:18:51 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 10 Feb 2013 15:18:51 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231859=3A_Re-arranging_uni?= =?utf-8?q?t_tests_to_separate_out_tests_that_cannot_pass_with_the?= Message-ID: <3Z3sgb1npXzPwq@mail.python.org> http://hg.python.org/jython/rev/50966417912b changeset: 7032:50966417912b user: Alan Kennedy date: Sun Feb 10 14:17:24 2013 +0000 summary: #1859: Re-arranging unit tests to separate out tests that cannot pass with the current implementation files: Lib/test/test_zlib.py | 21 +++++++++++++++------ NEWS | 4 ++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -24,15 +24,21 @@ self.assertEqual(zlib.crc32("", 1), 1) self.assertEqual(zlib.crc32("", 432), 432) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") + def test_adler32(self): + self.assertEqual(zlib.adler32(""), zlib.adler32("", 1)) + + @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ + which does not support a start value") def test_adler32start(self): - self.assertEqual(zlib.adler32(""), zlib.adler32("", 1)) self.assertTrue(zlib.adler32("abc", 0xffffffff)) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") def test_adler32empty(self): + self.assertEqual(zlib.adler32("", 1), 1) + + @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ + which does not support a start value") + def test_adler32empty_start(self): self.assertEqual(zlib.adler32("", 0), 0) - self.assertEqual(zlib.adler32("", 1), 1) self.assertEqual(zlib.adler32("", 432), 432) def assertEqual32(self, seen, expected): @@ -40,16 +46,19 @@ # This is important if bit 31 (0x08000000L) is set. self.assertEqual(seen & 0x0FFFFFFFFL, expected & 0x0FFFFFFFFL) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") def test_penguins(self): self.assertEqual32(zlib.crc32("penguin", 0), 0x0e5c1a120L) self.assertEqual32(zlib.crc32("penguin", 1), 0x43b6aa94) - self.assertEqual32(zlib.adler32("penguin", 0), 0x0bcf02f6) self.assertEqual32(zlib.adler32("penguin", 1), 0x0bd602f7) self.assertEqual(zlib.crc32("penguin"), zlib.crc32("penguin", 0)) self.assertEqual(zlib.adler32("penguin"),zlib.adler32("penguin",1)) + @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ + which does not support a start value") + def test_penguins_start(self): + self.assertEqual32(zlib.adler32("penguin", 0), 0x0bcf02f6) + def test_abcdefghijklmnop(self): """test issue1202 compliance: signed crc32, adler32 in 2.x""" foo = 'abcdefghijklmnop' diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -1,5 +1,9 @@ Jython NEWS +Jython 2.7b2 + Bugs Fixed + - [ 1753 ] zlib doesn't call end() on compress and decompress + Jython 2.7b1 Bugs Fixed - [ 1716 ] xrange slicing raises NPE. -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 15:38:10 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 10 Feb 2013 15:38:10 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231859=3A_Re-arranging_uni?= =?utf-8?q?t_tests_which_test_for_cpython-specific_inconsistent?= Message-ID: <3Z3t5t4xnmzR75@mail.python.org> http://hg.python.org/jython/rev/733074a31d77 changeset: 7033:733074a31d77 user: Alan Kennedy date: Sun Feb 10 14:36:42 2013 +0000 summary: #1859: Re-arranging unit tests which test for cpython-specific inconsistent behaviour files: Lib/test/test_zlib.py | 16 +++++++++++++--- 1 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -155,7 +155,8 @@ x = zlib.compress(data) self.assertEqual(zlib.decompress(x), data) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") + @unittest.skipIf(is_jython, "jython uses java.util.zip.Inflater, \ + which accepts incomplete streams without error") def test_incomplete_stream(self): # An useful error message is given x = zlib.compress(HAMLET_SCENE) @@ -398,13 +399,22 @@ dco = zlib.decompressobj() self.assertEqual(dco.flush(), "") # Returns nothing - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") def test_decompress_incomplete_stream(self): # This is 'foo', deflated x = 'x\x9cK\xcb\xcf\x07\x00\x02\x82\x01E' # For the record self.assertEqual(zlib.decompress(x), 'foo') - self.assertRaises(zlib.error, zlib.decompress, x[:-5]) + if not is_jython: + # There is inconsistency between cpython zlib.decompress (which does not accept + # incomplete streams) and zlib.decompressobj().decompress (which does accept + # incomplete streams, the whole point of this test) + # On jython, both zlib.decompress and zlib.decompressobject().decompress behave + # the same way: they both accept incomplete streams. + # Therefore, imposing this precondition is cpython specific + # and not appropriate on jython, which has consistent behaviour. + # http://bugs.python.org/issue8672 + # http://bugs.jython.org/issue1859 + self.assertRaises(zlib.error, zlib.decompress, x[:-5]) # Omitting the stream end works with decompressor objects # (see issue #8672). dco = zlib.decompressobj() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Feb 10 16:47:26 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sun, 10 Feb 2013 16:47:26 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231859=3A_Added_some_comme?= =?utf-8?q?nts_=28maybe=29_hanging_on_some_platforms=2C_but_windows?= Message-ID: <3Z3vdp6W6bzPfT@mail.python.org> http://hg.python.org/jython/rev/6b4a1088566e changeset: 7034:6b4a1088566e user: Alan Kennedy date: Sun Feb 10 15:45:56 2013 +0000 summary: #1859: Added some comments (maybe) hanging on some platforms, but windows files: Lib/test/test_zlib.py | 37 +++++++++++++++++++++++++++--- 1 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -28,7 +28,7 @@ self.assertEqual(zlib.adler32(""), zlib.adler32("", 1)) @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ - which does not support a start value") + which does not support a start value other than 1") def test_adler32start(self): self.assertTrue(zlib.adler32("abc", 0xffffffff)) @@ -36,7 +36,7 @@ self.assertEqual(zlib.adler32("", 1), 1) @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ - which does not support a start value") + which does not support a start value other than 1") def test_adler32empty_start(self): self.assertEqual(zlib.adler32("", 0), 0) self.assertEqual(zlib.adler32("", 432), 432) @@ -55,7 +55,7 @@ self.assertEqual(zlib.adler32("penguin"),zlib.adler32("penguin",1)) @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ - which does not support a start value") + which does not support a start value other than 1") def test_penguins_start(self): self.assertEqual32(zlib.adler32("penguin", 0), 0x0bcf02f6) @@ -126,7 +126,16 @@ # Release memory data = None - @unittest.skipIf(is_jython, "FIXME #1859: appears to hang on Jython") + # + # This test is working on jython 2.7 on windows, i.e. it does not hang + # Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) + # [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 + # Which poses the question: on which platforms does it hang + # Leaving skip in place for now. + # See note below about the size parameter and real_max_memuse below + # + + @unittest.skipIf(is_jython, "FIXME #1859: appears to hang on Jython on some platforms but not windows") def check_big_decompress_buffer(self, size, decompress_func): data = 'x' * size try: @@ -173,6 +182,16 @@ @precisionbigmemtest(size=_1G + 1024 * 1024, memuse=2) def test_big_decompress_buffer(self, size): + """ + This is NOT testing for a 'size=_1G + 1024 * 1024', because of the definition of + the precisionbigmemtest decorator, which resets the value to 5147, based on + the definition of test_support.real_max_memuse == 0 + This is the case on my windows installation of python 2.7.3. + Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32 + And on my build of jython 2.7 + Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) + [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 + """ self.check_big_decompress_buffer(size, zlib.decompress) @@ -492,6 +511,16 @@ @precisionbigmemtest(size=_1G + 1024 * 1024, memuse=2) def test_big_decompress_buffer(self, size): + """ + This is NOT testing for a 'size=_1G + 1024 * 1024', because of the definition of + the precisionbigmemtest decorator, which resets the value to 5147, based on + the definition of test_support.real_max_memuse == 0 + This is the case on my windows installation of python 2.7.3. + Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32 + And on my build of jython 2.7 + Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) + [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 + """ d = zlib.decompressobj() decompress = lambda s: d.decompress(s) + d.flush() self.check_big_decompress_buffer(size, decompress) -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 00:14:35 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Feb 2013 00:14:35 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Unskipped_test_that_works_f?= =?utf-8?q?or_me=2C_moved_and_added_detail_to_the_skip_comment=2E?= Message-ID: <3Z45Yl3HSwzSgj@mail.python.org> http://hg.python.org/jython/rev/262cd43957d7 changeset: 7035:262cd43957d7 user: Frank Wierzbicki date: Sun Feb 10 15:14:18 2013 -0800 summary: Unskipped test that works for me, moved and added detail to the skip comment. files: Lib/test/test_zlib.py | 21 +++++++++++---------- 1 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -110,6 +110,17 @@ class BaseCompressTestCase(object): + + # This test is working on jython 2.7 on windows, i.e. it does not hang + # Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) + # [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 + # Which poses the question: on which platforms does it hang + # Leaving skip in place for now. + # See note below about the size parameter and real_max_memuse below + # + # This test appears to hang on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 @unittest.skipIf(is_jython, "FIXME #1859: appears to hang on Jython") def check_big_compress_buffer(self, size, compress_func): _1M = 1024 * 1024 @@ -126,16 +137,6 @@ # Release memory data = None - # - # This test is working on jython 2.7 on windows, i.e. it does not hang - # Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) - # [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 - # Which poses the question: on which platforms does it hang - # Leaving skip in place for now. - # See note below about the size parameter and real_max_memuse below - # - - @unittest.skipIf(is_jython, "FIXME #1859: appears to hang on Jython on some platforms but not windows") def check_big_decompress_buffer(self, size, decompress_func): data = 'x' * size try: -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 00:29:45 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Feb 2013 00:29:45 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Remove_tests_for_=5Fwarning?= =?utf-8?q?s=2C_was_faked_anyway=2E?= Message-ID: <3Z45vF5rR0zSh6@mail.python.org> http://hg.python.org/jython/rev/28a51c59ac3c changeset: 7036:28a51c59ac3c user: Frank Wierzbicki date: Sun Feb 10 15:29:17 2013 -0800 summary: Remove tests for _warnings, was faked anyway. files: Lib/_warnings.py | 388 -------------------------- Lib/test/test_warnings.py | 11 +- 2 files changed, 10 insertions(+), 389 deletions(-) diff --git a/Lib/_warnings.py b/Lib/_warnings.py deleted file mode 100644 --- a/Lib/_warnings.py +++ /dev/null @@ -1,388 +0,0 @@ -""" -XXX: faked out Java part of the warnings subsystem, mostly copied from - warnings.py. TODO: rewrite in Java! -""" - -# Note: function level imports should *not* be used -# in this module as it may cause import lock deadlock. -# See bug 683658. -import linecache -import sys -import types - -__all__ = ["filters", "default_action", "once_registry", "warn", - "warn_explicit"] - -onceregistry = once_registry = {} -defaultaction = default_action = "default" - -def warnpy3k(message, category=None, stacklevel=1): - """Issue a deprecation warning for Python 3.x related changes. - - Warnings are omitted unless Python is started with the -3 option. - """ - if sys.py3kwarning: - if category is None: - category = DeprecationWarning - warn(message, category, stacklevel+1) - -def _show_warning(message, category, filename, lineno, file=None, line=None): - """Hook to write a warning to a file; replace if you like.""" - if file is None: - file = sys.stderr - try: - file.write(formatwarning(message, category, filename, lineno, line)) - except IOError: - pass # the file (probably stderr) is invalid - this warning gets lost. -# Keep a working version around in case the deprecation of the old API is -# triggered. -showwarning = _show_warning - -def formatwarning(message, category, filename, lineno, line=None): - """Function to format a warning the standard way.""" - s = "%s:%s: %s: %s\n" % (filename, lineno, category.__name__, message) - line = linecache.getline(filename, lineno) if line is None else line - if line: - line = line.strip() - s += " %s\n" % line - return s - -def filterwarnings(action, message="", category=Warning, module="", lineno=0, - append=0): - """Insert an entry into the list of warnings filters (at the front). - - 'action' -- one of "error", "ignore", "always", "default", "module", - or "once" - 'message' -- a regex that the warning message must match - 'category' -- a class that the warning must be a subclass of - 'module' -- a regex that the module name must match - 'lineno' -- an integer line number, 0 matches all warnings - 'append' -- if true, append to the list of filters - """ - import re - assert action in ("error", "ignore", "always", "default", "module", - "once"), "invalid action: %r" % (action,) - assert isinstance(message, basestring), "message must be a string" - assert isinstance(category, (type, types.ClassType)), \ - "category must be a class" - assert issubclass(category, Warning), "category must be a Warning subclass" - assert isinstance(module, basestring), "module must be a string" - assert isinstance(lineno, int) and lineno >= 0, \ - "lineno must be an int >= 0" - item = (action, re.compile(message, re.I), category, - re.compile(module), lineno) - if append: - filters.append(item) - else: - filters.insert(0, item) - -def simplefilter(action, category=Warning, lineno=0, append=0): - """Insert a simple entry into the list of warnings filters (at the front). - - A simple filter matches all modules and messages. - 'action' -- one of "error", "ignore", "always", "default", "module", - or "once" - 'category' -- a class that the warning must be a subclass of - 'lineno' -- an integer line number, 0 matches all warnings - 'append' -- if true, append to the list of filters - """ - assert action in ("error", "ignore", "always", "default", "module", - "once"), "invalid action: %r" % (action,) - assert isinstance(lineno, int) and lineno >= 0, \ - "lineno must be an int >= 0" - item = (action, None, category, None, lineno) - if append: - filters.append(item) - else: - filters.insert(0, item) - -def resetwarnings(): - """Clear the list of warning filters, so that no filters are active.""" - filters[:] = [] - -class _OptionError(Exception): - """Exception used by option processing helpers.""" - pass - -# Helper to process -W options passed via sys.warnoptions -def _processoptions(args): - for arg in args: - try: - _setoption(arg) - except _OptionError, msg: - print >>sys.stderr, "Invalid -W option ignored:", msg - -# Helper for _processoptions() -def _setoption(arg): - import re - parts = arg.split(':') - if len(parts) > 5: - raise _OptionError("too many fields (max 5): %r" % (arg,)) - while len(parts) < 5: - parts.append('') - action, message, category, module, lineno = [s.strip() - for s in parts] - action = _getaction(action) - message = re.escape(message) - category = _getcategory(category) - module = re.escape(module) - if module: - module = module + '$' - if lineno: - try: - lineno = int(lineno) - if lineno < 0: - raise ValueError - except (ValueError, OverflowError): - raise _OptionError("invalid lineno %r" % (lineno,)) - else: - lineno = 0 - filterwarnings(action, message, category, module, lineno) - -# Helper for _setoption() -def _getaction(action): - if not action: - return "default" - if action == "all": return "always" # Alias - for a in ('default', 'always', 'ignore', 'module', 'once', 'error'): - if a.startswith(action): - return a - raise _OptionError("invalid action: %r" % (action,)) - -# Helper for _setoption() -def _getcategory(category): - import re - if not category: - return Warning - if re.match("^[a-zA-Z0-9_]+$", category): - try: - cat = eval(category) - except NameError: - raise _OptionError("unknown warning category: %r" % (category,)) - else: - i = category.rfind(".") - module = category[:i] - klass = category[i+1:] - try: - m = __import__(module, None, None, [klass]) - except ImportError: - raise _OptionError("invalid module name: %r" % (module,)) - try: - cat = getattr(m, klass) - except AttributeError: - raise _OptionError("unknown warning category: %r" % (category,)) - if not issubclass(cat, Warning): - raise _OptionError("invalid warning category: %r" % (category,)) - return cat - -class SysGlobals: - '''sys.__dict__ values are reflectedfields, so we use this.''' - def __getitem__(self, key): - try: - return getattr(sys, key) - except AttributeError: - raise KeyError(key) - - def get(self, key, default=None): - if key in self: - return self[key] - return default - - def setdefault(self, key, default=None): - if key not in self: - sys.__dict__[key] = default - return self[key] - - def __contains__(self, key): - return key in sys.__dict__ - -# Code typically replaced by _warnings -def warn(message, category=None, stacklevel=1): - """Issue a warning, or maybe ignore it or raise an exception.""" - # Check if message is already a Warning object - if isinstance(message, Warning): - category = message.__class__ - # Check category argument - if category is None: - category = UserWarning - assert issubclass(category, Warning) - # Get context information - try: - caller = sys._getframe(stacklevel) - except ValueError: - globals = SysGlobals() - lineno = 1 - else: - globals = caller.f_globals - lineno = caller.f_lineno - if '__name__' in globals: - module = globals['__name__'] - else: - module = "" - filename = globals.get('__file__') - if filename: - fnl = filename.lower() - if fnl.endswith((".pyc", ".pyo")): - filename = filename[:-1] - elif fnl.endswith("$py.class"): - filename = filename[:-9] + '.py' - else: - if module == "__main__": - try: - filename = sys.argv[0] - except (AttributeError, TypeError): - # embedded interpreters don't have sys.argv, see bug #839151 - filename = '__main__' - if not filename: - filename = module - registry = globals.setdefault("__warningregistry__", {}) - warn_explicit(message, category, filename, lineno, module, registry, - globals) - -def warn_explicit(message, category, filename, lineno, - module=None, registry=None, module_globals=None): - lineno = int(lineno) - if module is None: - module = filename or "" - if module[-3:].lower() == ".py": - module = module[:-3] # XXX What about leading pathname? - if registry is None: - registry = {} - if isinstance(message, Warning): - text = str(message) - category = message.__class__ - else: - text = message - message = category(message) - key = (text, category, lineno) - # Quick test for common case - if registry.get(key): - return - # Search the filters - for item in filters: - action, msg, cat, mod, ln = item - if ((msg is None or msg.match(text)) and - issubclass(category, cat) and - (mod is None or mod.match(module)) and - (ln == 0 or lineno == ln)): - break - else: - action = defaultaction - # Early exit actions - if action == "ignore": - registry[key] = 1 - return - - # Prime the linecache for formatting, in case the - # "file" is actually in a zipfile or something. - linecache.getlines(filename, module_globals) - - if action == "error": - raise message - # Other actions - if action == "once": - _onceregistry = globals().get('onceregistry', once_registry) - registry[key] = 1 - oncekey = (text, category) - if _onceregistry.get(oncekey): - return - _onceregistry[oncekey] = 1 - elif action == "always": - pass - elif action == "module": - registry[key] = 1 - altkey = (text, category, 0) - if registry.get(altkey): - return - registry[altkey] = 1 - elif action == "default": - registry[key] = 1 - else: - # Unrecognized actions are errors - raise RuntimeError( - "Unrecognized action (%r) in warnings.filters:\n %s" % - (action, item)) - # Print message and context - fn = globals().get('showwarning', _show_warning) - fn(message, category, filename. lineno) - - -class WarningMessage(object): - - """Holds the result of a single showwarning() call.""" - - _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", - "line") - - def __init__(self, message, category, filename, lineno, file=None, - line=None): - local_values = locals() - for attr in self._WARNING_DETAILS: - setattr(self, attr, local_values[attr]) - self._category_name = category.__name__ if category else None - - def __str__(self): - return ("{message : %r, category : %r, filename : %r, lineno : %s, " - "line : %r}" % (self.message, self._category_name, - self.filename, self.lineno, self.line)) - - -class catch_warnings(object): - - """A context manager that copies and restores the warnings filter upon - exiting the context. - - The 'record' argument specifies whether warnings should be captured by a - custom implementation of warnings.showwarning() and be appended to a list - returned by the context manager. Otherwise None is returned by the context - manager. The objects appended to the list are arguments whose attributes - mirror the arguments to showwarning(). - - The 'module' argument is to specify an alternative module to the module - named 'warnings' and imported under that name. This argument is only useful - when testing the warnings module itself. - - """ - - def __init__(self, record=False, module=None): - """Specify whether to record warnings and if an alternative module - should be used other than sys.modules['warnings']. - - For compatibility with Python 3.0, please consider all arguments to be - keyword-only. - - """ - self._record = record - self._module = sys.modules['warnings'] if module is None else module - self._entered = False - - def __repr__(self): - args = [] - if self._record: - args.append("record=True") - if self._module is not sys.modules['warnings']: - args.append("module=%r" % self._module) - name = type(self).__name__ - return "%s(%s)" % (name, ", ".join(args)) - - def __enter__(self): - if self._entered: - raise RuntimeError("Cannot enter %r twice" % self) - self._entered = True - self._filters = self._module.filters - self._module.filters = self._module._filters = self._filters[:] - self._showwarning = self._module.showwarning - if self._record: - log = [] - def showwarning(*args, **kwargs): - log.append(WarningMessage(*args, **kwargs)) - self._module.showwarning = showwarning - return log - else: - return None - - def __exit__(self, *exc_info): - if not self._entered: - raise RuntimeError("Cannot exit %r without entering first" % self) - self._module.filters = self._module._filters = self._filters - self._module.showwarning = self._showwarning diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -189,6 +189,7 @@ self.assertEqual(str(w[-1].message), text) self.assertTrue(w[-1].category is UserWarning) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CFilterTests(BaseTest, FilterTests): module = c_warnings @@ -351,6 +352,7 @@ self.module.warn(BadStrWarning()) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CWarnTests(BaseTest, WarnTests): module = c_warnings @@ -403,6 +405,7 @@ self.assertFalse(out.strip()) self.assertNotIn(b'RuntimeWarning', err) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CWCmdLineTests(BaseTest, WCmdLineTests): module = c_warnings @@ -410,6 +413,7 @@ module = py_warnings + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class _WarningsTests(BaseTest): """Tests specific to the _warnings module.""" @@ -593,6 +597,7 @@ file_object, expected_file_line) self.assertEqual(expect, file_object.getvalue()) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CWarningsDisplayTests(BaseTest, WarningsDisplayTests): module = c_warnings @@ -703,6 +708,7 @@ wmod.warn("foo") + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CCatchWarningTests(CatchWarningTests): module = c_warnings @@ -742,6 +748,7 @@ "['ignore::UnicodeWarning', 'ignore::DeprecationWarning']") self.assertEqual(p.wait(), 0) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CEnvironmentVariableTests(EnvironmentVariableTests): module = c_warnings @@ -751,7 +758,9 @@ def test_main(): py_warnings.onceregistry.clear() - c_warnings.onceregistry.clear() + # No _warnings in _jython yet. + if not test_support.is_jython: + c_warnings.onceregistry.clear() test_support.run_unittest( CFilterTests, PyFilterTests, -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 05:10:45 2013 From: jython-checkins at python.org (alan.kennedy) Date: Mon, 11 Feb 2013 05:10:45 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231859=3A_Fixed_test_hang_?= =?utf-8?q?that_was_caused_by_an_issue_unrelated_to_the_functions?= Message-ID: <3Z4D7T4kqwzQJG@mail.python.org> http://hg.python.org/jython/rev/eef625ddc617 changeset: 7037:eef625ddc617 user: Alan Kennedy date: Mon Feb 11 04:09:05 2013 +0000 summary: #1859: Fixed test hang that was caused by an issue unrelated to the functions under test files: Lib/test/test_zlib.py | 44 ++++++++++++++++++------------ 1 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -111,26 +111,34 @@ class BaseCompressTestCase(object): - # This test is working on jython 2.7 on windows, i.e. it does not hang - # Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) - # [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 - # Which poses the question: on which platforms does it hang - # Leaving skip in place for now. - # See note below about the size parameter and real_max_memuse below - # - # This test appears to hang on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(is_jython, "FIXME #1859: appears to hang on Jython") def check_big_compress_buffer(self, size, compress_func): _1M = 1024 * 1024 - fmt = "%%0%dx" % (2 * _1M) - # Generate 10MB worth of random, and expand it by repeating it. - # The assumption is that zlib's memory is not big enough to exploit - # such spread out redundancy. - data = ''.join([binascii.a2b_hex(fmt % random.getrandbits(8 * _1M)) - for i in range(10)]) - data = data * (size // len(data) + 1) + if not is_jython: + # Generate 10MB worth of random, and expand it by repeating it. + # The assumption is that zlib's memory is not big enough to exploit + # such spread out redundancy. + fmt = "%%0%dx" % (2 * _1M) + data = ''.join([binascii.a2b_hex(fmt % random.getrandbits(8 * _1M)) + for i in range(10)]) + data = data * (size // len(data) + 1) + else: + # + # The original version of this test passes fine on cpython, + # but appears to hang on jython, because of the time taken to + # format a very large integer as a hexadecimal string. + # See this issue for details + # http://bugs.jython.org/issue2013 + # Since testing string formatting is not the purpose of the test + # it is necessary to generate the random test data in a different + # way on jython. (There may be a better way than what I have + # implemented here) + # + from java.math import BigInteger + from java.util import Random + num_bits = 8 * _1M # causes "java.lang.OutOfMemoryError: Java heap space" + num_bits = _1M + data = ''.join([str(BigInteger((num_bits), Random()).toByteArray()) + for i in range(10)]) try: compress_func(data) finally: -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 06:33:52 2013 From: jython-checkins at python.org (philip.jenvey) Date: Mon, 11 Feb 2013 06:33:52 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_idna_is_issue1153?= Message-ID: <3Z4FzN5dnXzSch@mail.python.org> http://hg.python.org/jython/rev/6a3dc0ce56b6 changeset: 7038:6a3dc0ce56b6 user: Philip Jenvey date: Sun Feb 10 21:33:30 2013 -0800 summary: idna is issue1153 files: Lib/test/test_codecs.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -1084,7 +1084,7 @@ except Exception,e: raise test_support.TestFailed("Test 3.%d: %s" % (pos+1, str(e))) - at unittest.skipIf(test_support.is_jython, "FIXME: Jython issue 2000 missing support for IDNA") + at unittest.skipIf(test_support.is_jython, "FIXME: Jython issue 1153 missing support for IDNA") class IDNACodecTest(unittest.TestCase): def test_builtin_decode(self): self.assertEqual(unicode("python.org", "idna"), u"python.org") -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 21:54:11 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Feb 2013 21:54:11 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Skip_test=5Furllib2_test_th?= =?utf-8?q?at_doesn=27t_work_for_me_currently=2E?= Message-ID: <3Z4fPH6pthzPX4@mail.python.org> http://hg.python.org/jython/rev/9f966b8ab491 changeset: 7039:9f966b8ab491 user: Frank Wierzbicki date: Mon Feb 11 12:53:53 2013 -0800 summary: Skip test_urllib2 test that doesn't work for me currently. files: Lib/test/test_urllib2.py | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -663,6 +663,11 @@ self.assertEqual(headers.get("Content-type"), mimetype) self.assertEqual(int(headers["Content-length"]), len(data)) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(test_support.is_jython, + "FIXME: Currently not working on jython") def test_file(self): import rfc822, socket h = urllib2.FileHandler() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 22:20:29 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Feb 2013 22:20:29 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_from=3A?= Message-ID: <3Z4fzd0gfWzPWM@mail.python.org> http://hg.python.org/jython/rev/fe1e638a3cdf changeset: 7040:fe1e638a3cdf user: Frank Wierzbicki date: Mon Feb 11 13:20:12 2013 -0800 summary: from: http://hg.python.org/cpython/file/22db03646d9b/Lib/test/test_charmapcodec.py files: Lib/test/test_charmapcodec.py | 56 +++++++++++++++++++++++ 1 files changed, 56 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_charmapcodec.py b/Lib/test/test_charmapcodec.py new file mode 100644 --- /dev/null +++ b/Lib/test/test_charmapcodec.py @@ -0,0 +1,56 @@ +""" Python character mapping codec test + +This uses the test codec in testcodec.py and thus also tests the +encodings package lookup scheme. + +Written by Marc-Andre Lemburg (mal at lemburg.com). + +(c) Copyright 2000 Guido van Rossum. + +"""#" + +import test.test_support, unittest + +import codecs + +# Register a search function which knows about our codec +def codec_search_function(encoding): + if encoding == 'testcodec': + from test import testcodec + return tuple(testcodec.getregentry()) + return None + +codecs.register(codec_search_function) + +# test codec's name (see test/testcodec.py) +codecname = 'testcodec' + +class CharmapCodecTest(unittest.TestCase): + def test_constructorx(self): + self.assertEqual(unicode('abc', codecname), u'abc') + self.assertEqual(unicode('xdef', codecname), u'abcdef') + self.assertEqual(unicode('defx', codecname), u'defabc') + self.assertEqual(unicode('dxf', codecname), u'dabcf') + self.assertEqual(unicode('dxfx', codecname), u'dabcfabc') + + def test_encodex(self): + self.assertEqual(u'abc'.encode(codecname), 'abc') + self.assertEqual(u'xdef'.encode(codecname), 'abcdef') + self.assertEqual(u'defx'.encode(codecname), 'defabc') + self.assertEqual(u'dxf'.encode(codecname), 'dabcf') + self.assertEqual(u'dxfx'.encode(codecname), 'dabcfabc') + + def test_constructory(self): + self.assertEqual(unicode('ydef', codecname), u'def') + self.assertEqual(unicode('defy', codecname), u'def') + self.assertEqual(unicode('dyf', codecname), u'df') + self.assertEqual(unicode('dyfy', codecname), u'df') + + def test_maptoundefined(self): + self.assertRaises(UnicodeError, unicode, 'abc\001', codecname) + +def test_main(): + test.test_support.run_unittest(CharmapCodecTest) + +if __name__ == "__main__": + test_main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 22:23:30 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Feb 2013 22:23:30 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Skipping_test_in_test=5Fcha?= =?utf-8?q?rmapcodec=2E?= Message-ID: <3Z4g363vC5zPfT@mail.python.org> http://hg.python.org/jython/rev/7f4f0d642785 changeset: 7041:7f4f0d642785 user: Frank Wierzbicki date: Mon Feb 11 13:23:08 2013 -0800 summary: Skipping test in test_charmapcodec. files: Lib/test/test_charmapcodec.py | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_charmapcodec.py b/Lib/test/test_charmapcodec.py --- a/Lib/test/test_charmapcodec.py +++ b/Lib/test/test_charmapcodec.py @@ -40,6 +40,11 @@ self.assertEqual(u'dxf'.encode(codecname), 'dabcf') self.assertEqual(u'dxfx'.encode(codecname), 'dabcfabc') + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(test.test_support.is_jython, + "FIXME: Currently not working on jython") def test_constructory(self): self.assertEqual(unicode('ydef', codecname), u'def') self.assertEqual(unicode('defy', codecname), u'def') -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 22:40:22 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Feb 2013 22:40:22 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_from=3A?= Message-ID: <3Z4gQZ3kh4zPVd@mail.python.org> http://hg.python.org/jython/rev/ace893662af0 changeset: 7042:ace893662af0 user: Frank Wierzbicki date: Mon Feb 11 13:35:28 2013 -0800 summary: from: http://hg.python.org/cpython/file/22db03646d9b/Lib/test/test_memoryio.py files: Lib/test/test_memoryio.py | 707 ++++++++++++++++++++++++++ 1 files changed, 707 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py new file mode 100644 --- /dev/null +++ b/Lib/test/test_memoryio.py @@ -0,0 +1,707 @@ +"""Unit tests for memory-based file-like objects. +StringIO -- for unicode strings +BytesIO -- for bytes +""" + +from __future__ import unicode_literals +from __future__ import print_function + +import unittest +from test import test_support as support + +import io +import _pyio as pyio +import pickle + +class MemorySeekTestMixin: + + def testInit(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + def testRead(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + self.assertEqual(buf[:1], bytesIo.read(1)) + self.assertEqual(buf[1:5], bytesIo.read(4)) + self.assertEqual(buf[5:], bytesIo.read(900)) + self.assertEqual(self.EOF, bytesIo.read()) + + def testReadNoArgs(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + self.assertEqual(buf, bytesIo.read()) + self.assertEqual(self.EOF, bytesIo.read()) + + def testSeek(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + bytesIo.read(5) + bytesIo.seek(0) + self.assertEqual(buf, bytesIo.read()) + + bytesIo.seek(3) + self.assertEqual(buf[3:], bytesIo.read()) + self.assertRaises(TypeError, bytesIo.seek, 0.0) + + def testTell(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + self.assertEqual(0, bytesIo.tell()) + bytesIo.seek(5) + self.assertEqual(5, bytesIo.tell()) + bytesIo.seek(10000) + self.assertEqual(10000, bytesIo.tell()) + + +class MemoryTestMixin: + + def test_detach(self): + buf = self.ioclass() + self.assertRaises(self.UnsupportedOperation, buf.detach) + + def write_ops(self, f, t): + self.assertEqual(f.write(t("blah.")), 5) + self.assertEqual(f.seek(0), 0) + self.assertEqual(f.write(t("Hello.")), 6) + self.assertEqual(f.tell(), 6) + self.assertEqual(f.seek(5), 5) + self.assertEqual(f.tell(), 5) + self.assertEqual(f.write(t(" world\n\n\n")), 9) + self.assertEqual(f.seek(0), 0) + self.assertEqual(f.write(t("h")), 1) + self.assertEqual(f.truncate(12), 12) + self.assertEqual(f.tell(), 1) + + def test_write(self): + buf = self.buftype("hello world\n") + memio = self.ioclass(buf) + + self.write_ops(memio, self.buftype) + self.assertEqual(memio.getvalue(), buf) + memio = self.ioclass() + self.write_ops(memio, self.buftype) + self.assertEqual(memio.getvalue(), buf) + self.assertRaises(TypeError, memio.write, None) + memio.close() + self.assertRaises(ValueError, memio.write, self.buftype("")) + + def test_writelines(self): + buf = self.buftype("1234567890") + memio = self.ioclass() + + self.assertEqual(memio.writelines([buf] * 100), None) + self.assertEqual(memio.getvalue(), buf * 100) + memio.writelines([]) + self.assertEqual(memio.getvalue(), buf * 100) + memio = self.ioclass() + self.assertRaises(TypeError, memio.writelines, [buf] + [1]) + self.assertEqual(memio.getvalue(), buf) + self.assertRaises(TypeError, memio.writelines, None) + memio.close() + self.assertRaises(ValueError, memio.writelines, []) + + def test_writelines_error(self): + memio = self.ioclass() + def error_gen(): + yield self.buftype('spam') + raise KeyboardInterrupt + + self.assertRaises(KeyboardInterrupt, memio.writelines, error_gen()) + + def test_truncate(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertRaises(ValueError, memio.truncate, -1) + memio.seek(6) + self.assertEqual(memio.truncate(), 6) + self.assertEqual(memio.getvalue(), buf[:6]) + self.assertEqual(memio.truncate(4), 4) + self.assertEqual(memio.getvalue(), buf[:4]) + # truncate() accepts long objects + self.assertEqual(memio.truncate(4L), 4) + self.assertEqual(memio.getvalue(), buf[:4]) + self.assertEqual(memio.tell(), 6) + memio.seek(0, 2) + memio.write(buf) + self.assertEqual(memio.getvalue(), buf[:4] + buf) + pos = memio.tell() + self.assertEqual(memio.truncate(None), pos) + self.assertEqual(memio.tell(), pos) + self.assertRaises(TypeError, memio.truncate, '0') + memio.close() + self.assertRaises(ValueError, memio.truncate, 0) + + def test_init(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + self.assertEqual(memio.getvalue(), buf) + memio = self.ioclass(None) + self.assertEqual(memio.getvalue(), self.EOF) + memio.__init__(buf * 2) + self.assertEqual(memio.getvalue(), buf * 2) + memio.__init__(buf) + self.assertEqual(memio.getvalue(), buf) + + def test_read(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.read(0), self.EOF) + self.assertEqual(memio.read(1), buf[:1]) + # read() accepts long objects + self.assertEqual(memio.read(4L), buf[1:5]) + self.assertEqual(memio.read(900), buf[5:]) + self.assertEqual(memio.read(), self.EOF) + memio.seek(0) + self.assertEqual(memio.read(), buf) + self.assertEqual(memio.read(), self.EOF) + self.assertEqual(memio.tell(), 10) + memio.seek(0) + self.assertEqual(memio.read(-1), buf) + memio.seek(0) + self.assertEqual(type(memio.read()), type(buf)) + memio.seek(100) + self.assertEqual(type(memio.read()), type(buf)) + memio.seek(0) + self.assertEqual(memio.read(None), buf) + self.assertRaises(TypeError, memio.read, '') + memio.close() + self.assertRaises(ValueError, memio.read) + + def test_readline(self): + buf = self.buftype("1234567890\n") + memio = self.ioclass(buf * 2) + + self.assertEqual(memio.readline(0), self.EOF) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), self.EOF) + memio.seek(0) + self.assertEqual(memio.readline(5), buf[:5]) + # readline() accepts long objects + self.assertEqual(memio.readline(5L), buf[5:10]) + self.assertEqual(memio.readline(5), buf[10:15]) + memio.seek(0) + self.assertEqual(memio.readline(-1), buf) + memio.seek(0) + self.assertEqual(memio.readline(0), self.EOF) + + buf = self.buftype("1234567890\n") + memio = self.ioclass((buf * 3)[:-1]) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), buf[:-1]) + self.assertEqual(memio.readline(), self.EOF) + memio.seek(0) + self.assertEqual(type(memio.readline()), type(buf)) + self.assertEqual(memio.readline(), buf) + self.assertRaises(TypeError, memio.readline, '') + memio.close() + self.assertRaises(ValueError, memio.readline) + + def test_readlines(self): + buf = self.buftype("1234567890\n") + memio = self.ioclass(buf * 10) + + self.assertEqual(memio.readlines(), [buf] * 10) + memio.seek(5) + self.assertEqual(memio.readlines(), [buf[5:]] + [buf] * 9) + memio.seek(0) + # readlines() accepts long objects + self.assertEqual(memio.readlines(15L), [buf] * 2) + memio.seek(0) + self.assertEqual(memio.readlines(-1), [buf] * 10) + memio.seek(0) + self.assertEqual(memio.readlines(0), [buf] * 10) + memio.seek(0) + self.assertEqual(type(memio.readlines()[0]), type(buf)) + memio.seek(0) + self.assertEqual(memio.readlines(None), [buf] * 10) + self.assertRaises(TypeError, memio.readlines, '') + memio.close() + self.assertRaises(ValueError, memio.readlines) + + def test_iterator(self): + buf = self.buftype("1234567890\n") + memio = self.ioclass(buf * 10) + + self.assertEqual(iter(memio), memio) + self.assertTrue(hasattr(memio, '__iter__')) + self.assertTrue(hasattr(memio, 'next')) + i = 0 + for line in memio: + self.assertEqual(line, buf) + i += 1 + self.assertEqual(i, 10) + memio.seek(0) + i = 0 + for line in memio: + self.assertEqual(line, buf) + i += 1 + self.assertEqual(i, 10) + memio = self.ioclass(buf * 2) + memio.close() + self.assertRaises(ValueError, next, memio) + + def test_getvalue(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.getvalue(), buf) + memio.read() + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(type(memio.getvalue()), type(buf)) + memio = self.ioclass(buf * 1000) + self.assertEqual(memio.getvalue()[-3:], self.buftype("890")) + memio = self.ioclass(buf) + memio.close() + self.assertRaises(ValueError, memio.getvalue) + + def test_seek(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + memio.read(5) + self.assertRaises(ValueError, memio.seek, -1) + self.assertRaises(ValueError, memio.seek, 1, -1) + self.assertRaises(ValueError, memio.seek, 1, 3) + self.assertEqual(memio.seek(0), 0) + self.assertEqual(memio.seek(0, 0), 0) + self.assertEqual(memio.read(), buf) + self.assertEqual(memio.seek(3), 3) + # seek() accepts long objects + self.assertEqual(memio.seek(3L), 3) + self.assertEqual(memio.seek(0, 1), 3) + self.assertEqual(memio.read(), buf[3:]) + self.assertEqual(memio.seek(len(buf)), len(buf)) + self.assertEqual(memio.read(), self.EOF) + memio.seek(len(buf) + 1) + self.assertEqual(memio.read(), self.EOF) + self.assertEqual(memio.seek(0, 2), len(buf)) + self.assertEqual(memio.read(), self.EOF) + memio.close() + self.assertRaises(ValueError, memio.seek, 0) + + def test_overseek(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.seek(len(buf) + 1), 11) + self.assertEqual(memio.read(), self.EOF) + self.assertEqual(memio.tell(), 11) + self.assertEqual(memio.getvalue(), buf) + memio.write(self.EOF) + self.assertEqual(memio.getvalue(), buf) + memio.write(buf) + self.assertEqual(memio.getvalue(), buf + self.buftype('\0') + buf) + + def test_tell(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.tell(), 0) + memio.seek(5) + self.assertEqual(memio.tell(), 5) + memio.seek(10000) + self.assertEqual(memio.tell(), 10000) + memio.close() + self.assertRaises(ValueError, memio.tell) + + def test_flush(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.flush(), None) + + def test_flags(self): + memio = self.ioclass() + + self.assertEqual(memio.writable(), True) + self.assertEqual(memio.readable(), True) + self.assertEqual(memio.seekable(), True) + self.assertEqual(memio.isatty(), False) + self.assertEqual(memio.closed, False) + memio.close() + self.assertEqual(memio.writable(), True) + self.assertEqual(memio.readable(), True) + self.assertEqual(memio.seekable(), True) + self.assertRaises(ValueError, memio.isatty) + self.assertEqual(memio.closed, True) + + def test_subclassing(self): + buf = self.buftype("1234567890") + def test1(): + class MemIO(self.ioclass): + pass + m = MemIO(buf) + return m.getvalue() + def test2(): + class MemIO(self.ioclass): + def __init__(me, a, b): + self.ioclass.__init__(me, a) + m = MemIO(buf, None) + return m.getvalue() + self.assertEqual(test1(), buf) + self.assertEqual(test2(), buf) + + def test_instance_dict_leak(self): + # Test case for issue #6242. + # This will be caught by regrtest.py -R if this leak. + for _ in range(100): + memio = self.ioclass() + memio.foo = 1 + + def test_pickling(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + memio.foo = 42 + memio.seek(2) + + class PickleTestMemIO(self.ioclass): + def __init__(me, initvalue, foo): + self.ioclass.__init__(me, initvalue) + me.foo = foo + # __getnewargs__ is undefined on purpose. This checks that PEP 307 + # is used to provide pickling support. + + # Pickle expects the class to be on the module level. Here we use a + # little hack to allow the PickleTestMemIO class to derive from + # self.ioclass without having to define all combinations explicitly on + # the module-level. + import __main__ + PickleTestMemIO.__module__ = '__main__' + __main__.PickleTestMemIO = PickleTestMemIO + submemio = PickleTestMemIO(buf, 80) + submemio.seek(2) + + # We only support pickle protocol 2 and onward since we use extended + # __reduce__ API of PEP 307 to provide pickling support. + for proto in range(2, pickle.HIGHEST_PROTOCOL): + for obj in (memio, submemio): + obj2 = pickle.loads(pickle.dumps(obj, protocol=proto)) + self.assertEqual(obj.getvalue(), obj2.getvalue()) + self.assertEqual(obj.__class__, obj2.__class__) + self.assertEqual(obj.foo, obj2.foo) + self.assertEqual(obj.tell(), obj2.tell()) + obj.close() + self.assertRaises(ValueError, pickle.dumps, obj, proto) + del __main__.PickleTestMemIO + + +class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase): + + UnsupportedOperation = pyio.UnsupportedOperation + + @staticmethod + def buftype(s): + return s.encode("ascii") + ioclass = pyio.BytesIO + EOF = b"" + + def test_read1(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertRaises(TypeError, memio.read1) + self.assertEqual(memio.read(), buf) + + def test_readinto(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + b = bytearray(b"hello") + self.assertEqual(memio.readinto(b), 5) + self.assertEqual(b, b"12345") + self.assertEqual(memio.readinto(b), 5) + self.assertEqual(b, b"67890") + self.assertEqual(memio.readinto(b), 0) + self.assertEqual(b, b"67890") + b = bytearray(b"hello world") + memio.seek(0) + self.assertEqual(memio.readinto(b), 10) + self.assertEqual(b, b"1234567890d") + b = bytearray(b"") + memio.seek(0) + self.assertEqual(memio.readinto(b), 0) + self.assertEqual(b, b"") + self.assertRaises(TypeError, memio.readinto, '') + import array + a = array.array(b'b', b"hello world") + memio = self.ioclass(buf) + memio.readinto(a) + self.assertEqual(a.tostring(), b"1234567890d") + memio.close() + self.assertRaises(ValueError, memio.readinto, b) + memio = self.ioclass(b"123") + b = bytearray() + memio.seek(42) + memio.readinto(b) + self.assertEqual(b, b"") + + def test_relative_seek(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.seek(-1, 1), 0) + self.assertEqual(memio.seek(3, 1), 3) + self.assertEqual(memio.seek(-4, 1), 0) + self.assertEqual(memio.seek(-1, 2), 9) + self.assertEqual(memio.seek(1, 1), 10) + self.assertEqual(memio.seek(1, 2), 11) + memio.seek(-3, 2) + self.assertEqual(memio.read(), buf[-3:]) + memio.seek(0) + memio.seek(1, 1) + self.assertEqual(memio.read(), buf[1:]) + + def test_unicode(self): + memio = self.ioclass() + + self.assertRaises(TypeError, self.ioclass, "1234567890") + self.assertRaises(TypeError, memio.write, "1234567890") + self.assertRaises(TypeError, memio.writelines, ["1234567890"]) + + def test_bytes_array(self): + buf = b"1234567890" + import array + a = array.array(b'b', buf) + memio = self.ioclass(a) + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(memio.write(a), 10) + self.assertEqual(memio.getvalue(), buf) + + def test_issue5449(self): + buf = self.buftype("1234567890") + self.ioclass(initial_bytes=buf) + self.assertRaises(TypeError, self.ioclass, buf, foo=None) + + +class TextIOTestMixin: + + def test_newlines_property(self): + memio = self.ioclass(newline=None) + # The C StringIO decodes newlines in write() calls, but the Python + # implementation only does when reading. This function forces them to + # be decoded for testing. + def force_decode(): + memio.seek(0) + memio.read() + self.assertEqual(memio.newlines, None) + memio.write("a\n") + force_decode() + self.assertEqual(memio.newlines, "\n") + memio.write("b\r\n") + force_decode() + self.assertEqual(memio.newlines, ("\n", "\r\n")) + memio.write("c\rd") + force_decode() + self.assertEqual(memio.newlines, ("\r", "\n", "\r\n")) + + def test_relative_seek(self): + memio = self.ioclass() + + self.assertRaises(IOError, memio.seek, -1, 1) + self.assertRaises(IOError, memio.seek, 3, 1) + self.assertRaises(IOError, memio.seek, -3, 1) + self.assertRaises(IOError, memio.seek, -1, 2) + self.assertRaises(IOError, memio.seek, 1, 1) + self.assertRaises(IOError, memio.seek, 1, 2) + + def test_textio_properties(self): + memio = self.ioclass() + + # These are just dummy values but we nevertheless check them for fear + # of unexpected breakage. + self.assertIsNone(memio.encoding) + self.assertIsNone(memio.errors) + self.assertFalse(memio.line_buffering) + + def test_newline_none(self): + # newline=None + memio = self.ioclass("a\nb\r\nc\rd", newline=None) + self.assertEqual(list(memio), ["a\n", "b\n", "c\n", "d"]) + memio.seek(0) + self.assertEqual(memio.read(1), "a") + self.assertEqual(memio.read(2), "\nb") + self.assertEqual(memio.read(2), "\nc") + self.assertEqual(memio.read(1), "\n") + memio = self.ioclass(newline=None) + self.assertEqual(2, memio.write("a\n")) + self.assertEqual(3, memio.write("b\r\n")) + self.assertEqual(3, memio.write("c\rd")) + memio.seek(0) + self.assertEqual(memio.read(), "a\nb\nc\nd") + memio = self.ioclass("a\r\nb", newline=None) + self.assertEqual(memio.read(3), "a\nb") + + def test_newline_empty(self): + # newline="" + memio = self.ioclass("a\nb\r\nc\rd", newline="") + self.assertEqual(list(memio), ["a\n", "b\r\n", "c\r", "d"]) + memio.seek(0) + self.assertEqual(memio.read(4), "a\nb\r") + self.assertEqual(memio.read(2), "\nc") + self.assertEqual(memio.read(1), "\r") + memio = self.ioclass(newline="") + self.assertEqual(2, memio.write("a\n")) + self.assertEqual(2, memio.write("b\r")) + self.assertEqual(2, memio.write("\nc")) + self.assertEqual(2, memio.write("\rd")) + memio.seek(0) + self.assertEqual(list(memio), ["a\n", "b\r\n", "c\r", "d"]) + + def test_newline_lf(self): + # newline="\n" + memio = self.ioclass("a\nb\r\nc\rd") + self.assertEqual(list(memio), ["a\n", "b\r\n", "c\rd"]) + + def test_newline_cr(self): + # newline="\r" + memio = self.ioclass("a\nb\r\nc\rd", newline="\r") + self.assertEqual(memio.read(), "a\rb\r\rc\rd") + memio.seek(0) + self.assertEqual(list(memio), ["a\r", "b\r", "\r", "c\r", "d"]) + + def test_newline_crlf(self): + # newline="\r\n" + memio = self.ioclass("a\nb\r\nc\rd", newline="\r\n") + self.assertEqual(memio.read(), "a\r\nb\r\r\nc\rd") + memio.seek(0) + self.assertEqual(list(memio), ["a\r\n", "b\r\r\n", "c\rd"]) + + def test_issue5265(self): + # StringIO can duplicate newlines in universal newlines mode + memio = self.ioclass("a\r\nb\r\n", newline=None) + self.assertEqual(memio.read(5), "a\nb\n") + + +class PyStringIOTest(MemoryTestMixin, MemorySeekTestMixin, + TextIOTestMixin, unittest.TestCase): + buftype = unicode + ioclass = pyio.StringIO + UnsupportedOperation = pyio.UnsupportedOperation + EOF = "" + + +class PyStringIOPickleTest(TextIOTestMixin, unittest.TestCase): + """Test if pickle restores properly the internal state of StringIO. + """ + buftype = unicode + UnsupportedOperation = pyio.UnsupportedOperation + EOF = "" + + class ioclass(pyio.StringIO): + def __new__(cls, *args, **kwargs): + return pickle.loads(pickle.dumps(pyio.StringIO(*args, **kwargs))) + def __init__(self, *args, **kwargs): + pass + + +class CBytesIOTest(PyBytesIOTest): + ioclass = io.BytesIO + UnsupportedOperation = io.UnsupportedOperation + + test_bytes_array = unittest.skip( + "array.array() does not have the new buffer API" + )(PyBytesIOTest.test_bytes_array) + + + def test_getstate(self): + memio = self.ioclass() + state = memio.__getstate__() + self.assertEqual(len(state), 3) + bytearray(state[0]) # Check if state[0] supports the buffer interface. + self.assertIsInstance(state[1], int) + self.assertTrue(isinstance(state[2], dict) or state[2] is None) + memio.close() + self.assertRaises(ValueError, memio.__getstate__) + + def test_setstate(self): + # This checks whether __setstate__ does proper input validation. + memio = self.ioclass() + memio.__setstate__((b"no error", 0, None)) + memio.__setstate__((bytearray(b"no error"), 0, None)) + memio.__setstate__((b"no error", 0, {'spam': 3})) + self.assertRaises(ValueError, memio.__setstate__, (b"", -1, None)) + self.assertRaises(TypeError, memio.__setstate__, ("unicode", 0, None)) + self.assertRaises(TypeError, memio.__setstate__, (b"", 0.0, None)) + self.assertRaises(TypeError, memio.__setstate__, (b"", 0, 0)) + self.assertRaises(TypeError, memio.__setstate__, (b"len-test", 0)) + self.assertRaises(TypeError, memio.__setstate__) + self.assertRaises(TypeError, memio.__setstate__, 0) + memio.close() + self.assertRaises(ValueError, memio.__setstate__, (b"closed", 0, None)) + + +class CStringIOTest(PyStringIOTest): + ioclass = io.StringIO + UnsupportedOperation = io.UnsupportedOperation + + # XXX: For the Python version of io.StringIO, this is highly + # dependent on the encoding used for the underlying buffer. + def test_widechar(self): + buf = self.buftype("\U0002030a\U00020347") + memio = self.ioclass(buf) + + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(memio.write(buf), len(buf)) + self.assertEqual(memio.tell(), len(buf)) + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(memio.write(buf), len(buf)) + self.assertEqual(memio.tell(), len(buf) * 2) + self.assertEqual(memio.getvalue(), buf + buf) + + def test_getstate(self): + memio = self.ioclass() + state = memio.__getstate__() + self.assertEqual(len(state), 4) + self.assertIsInstance(state[0], unicode) + self.assertIsInstance(state[1], str) + self.assertIsInstance(state[2], int) + self.assertTrue(isinstance(state[3], dict) or state[3] is None) + memio.close() + self.assertRaises(ValueError, memio.__getstate__) + + def test_setstate(self): + # This checks whether __setstate__ does proper input validation. + memio = self.ioclass() + memio.__setstate__(("no error", "\n", 0, None)) + memio.__setstate__(("no error", "", 0, {'spam': 3})) + self.assertRaises(ValueError, memio.__setstate__, ("", "f", 0, None)) + self.assertRaises(ValueError, memio.__setstate__, ("", "", -1, None)) + self.assertRaises(TypeError, memio.__setstate__, (b"", "", 0, None)) + # trunk is more tolerant than py3k on the type of the newline param + #self.assertRaises(TypeError, memio.__setstate__, ("", b"", 0, None)) + self.assertRaises(TypeError, memio.__setstate__, ("", "", 0.0, None)) + self.assertRaises(TypeError, memio.__setstate__, ("", "", 0, 0)) + self.assertRaises(TypeError, memio.__setstate__, ("len-test", 0)) + self.assertRaises(TypeError, memio.__setstate__) + self.assertRaises(TypeError, memio.__setstate__, 0) + memio.close() + self.assertRaises(ValueError, memio.__setstate__, ("closed", "", 0, None)) + + +class CStringIOPickleTest(PyStringIOPickleTest): + UnsupportedOperation = io.UnsupportedOperation + + class ioclass(io.StringIO): + def __new__(cls, *args, **kwargs): + return pickle.loads(pickle.dumps(io.StringIO(*args, **kwargs), + protocol=2)) + def __init__(self, *args, **kwargs): + pass + + +def test_main(): + tests = [PyBytesIOTest, PyStringIOTest, CBytesIOTest, CStringIOTest, + PyStringIOPickleTest, CStringIOPickleTest] + support.run_unittest(*tests) + +if __name__ == '__main__': + test_main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 11 22:40:24 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Feb 2013 22:40:24 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Skip_test_that_don=27t_work?= =?utf-8?q?_for_me=2E?= Message-ID: <3Z4gQc0dLtzPXx@mail.python.org> http://hg.python.org/jython/rev/08ccd20a81a5 changeset: 7043:08ccd20a81a5 user: Frank Wierzbicki date: Mon Feb 11 13:40:05 2013 -0800 summary: Skip test that don't work for me. files: Lib/test/test_memoryio.py | 46 +++++++++++++++++++++++++++ 1 files changed, 46 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -60,6 +60,11 @@ class MemoryTestMixin: + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_detach(self): buf = self.ioclass() self.assertRaises(self.UnsupportedOperation, buf.detach) @@ -174,6 +179,11 @@ memio.close() self.assertRaises(ValueError, memio.read) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_readline(self): buf = self.buftype("1234567890\n") memio = self.ioclass(buf * 2) @@ -319,6 +329,11 @@ self.assertEqual(memio.flush(), None) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_flags(self): memio = self.ioclass() @@ -460,6 +475,11 @@ memio.seek(1, 1) self.assertEqual(memio.read(), buf[1:]) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_unicode(self): memio = self.ioclass() @@ -612,6 +632,11 @@ )(PyBytesIOTest.test_bytes_array) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -622,6 +647,11 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() @@ -645,6 +675,12 @@ # XXX: For the Python version of io.StringIO, this is highly # dependent on the encoding used for the underlying buffer. + + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_widechar(self): buf = self.buftype("\U0002030a\U00020347") memio = self.ioclass(buf) @@ -657,6 +693,11 @@ self.assertEqual(memio.tell(), len(buf) * 2) self.assertEqual(memio.getvalue(), buf + buf) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -668,6 +709,11 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(support.is_jython, + "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 12 00:33:19 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 12 Feb 2013 00:33:19 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Unskip_test_that_now_passes?= =?utf-8?q?=2E?= Message-ID: <3Z4jwv2BKKzPPw@mail.python.org> http://hg.python.org/jython/rev/ef5741dd9b6d changeset: 7044:ef5741dd9b6d user: Frank Wierzbicki date: Mon Feb 11 15:32:55 2013 -0800 summary: Unskip test that now passes. files: Lib/test/test_site.py | 1 - 1 files changed, 0 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -196,7 +196,6 @@ env=env) self.assertEqual(rc, 1) - @unittest.skipIf(is_jython, "FIXME: not on Jython yet.") def test_getuserbase(self): site.USER_BASE = None user_base = site.getuserbase() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 12 16:29:17 2013 From: jython-checkins at python.org (jeff.allen) Date: Tue, 12 Feb 2013 16:29:17 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_PyByteArray_and_BaseBytes_J?= =?utf-8?q?avadoc_improvements?= Message-ID: <3Z577x0nLNzPnB@mail.python.org> http://hg.python.org/jython/rev/7d8005de6b19 changeset: 7045:7d8005de6b19 parent: 7034:6b4a1088566e user: Jeff Allen date: Mon Feb 11 17:30:30 2013 +0000 summary: PyByteArray and BaseBytes Javadoc improvements Extensive documentation change, and some incidental code changes of no consequence. (Override tags and whitespace.) files: src/org/python/core/BaseBytes.java | 402 +++++++++----- src/org/python/core/PyByteArray.java | 401 +++++++------- 2 files changed, 459 insertions(+), 344 deletions(-) diff --git a/src/org/python/core/BaseBytes.java b/src/org/python/core/BaseBytes.java --- a/src/org/python/core/BaseBytes.java +++ b/src/org/python/core/BaseBytes.java @@ -9,12 +9,12 @@ import java.util.ListIterator; /** - * Base class for Jython bytearray (and bytes in due course) that provides most of the Java API, - * including Java List behaviour. Attempts to modify the contents through this API will throw a - * TypeError if the actual type of the object is not mutable. - *

- * It is possible for a Java client to treat this class as a List<PyInteger>, obtaining - * equivalent functionality to the Python interface in a Java paradigm. + * Base class for Jython bytearray (and bytes in due course) that provides + * most of the Java API, including Java {@link List} behaviour. Attempts to modify the contents + * through this API will throw a TypeError if the actual type of the object is not + * mutable. It is possible for a Java client to treat this class as a + * List<PyInteger>, obtaining equivalent functionality to the Python interface in a + * Java paradigm. *

* Subclasses must define (from {@link PySequence}): *

    @@ -29,11 +29,29 @@ *
  • {@link #delRange(int, int)}
  • *
* since the default implementations will otherwise throw an exception. + *

+ * Many of the methods implemented here are inherited or thinly wrapped by {@link PyByteArray}, + * which offers them as Java API, or exposes them as Python methods. These prototype Python methods + * mostly accept a {@link PyObject} as argument, where you might have expected a byte[] + * or BaseBytes, in order to accommodate the full range of types accepted by the Python + * equivalent: usually, any PyObject that implements {@link BufferProtocol}, providing + * a one-dimensional array of bytes, is an acceptable argument. In the documentation, the reader + * will often see the terms "byte array" or "object viewable as bytes" instead of + * BaseBytes when this broader scope is intended. + *

+ * Where the methods return a BaseBytes, this is will normally be an instance of the + * class of the object on which the method was actually called. For example {@link #capitalize()}, + * defined in BaseBytes to return a BaseBytes, actually returns a {@link PyByteArray} + * when applied to a bytearray. Or it may be that the method returns a + * PyList of instances of the target type, for example {@link #rpartition(PyObject)}. + * This is achieved by the sub-class defining {@link #getslice(int, int, int)} and + * {@link #getBuilder(int)} to return instances of its own type. See the documentation of particular + * methods for more information. */ public abstract class BaseBytes extends PySequence implements List { /** - * Simple constructor of empty zero-length array of defined type. + * Constructs a zero-length BaseBytes of explicitly-specified sub-type. * * @param type explicit Jython type */ @@ -44,7 +62,7 @@ } /** - * Simple constructor of zero-filled array of defined size and type. + * Constructs a zero-filled array of defined size and type. * * @param size required * @param type explicit Jython type @@ -56,7 +74,7 @@ } /** - * Construct byte array of defined type by copying values from int[]. + * Constructs a byte array of defined type by copying values from int[]. * * @param type explicit Jython type * @param value source of values (and size) @@ -72,8 +90,8 @@ } /** - * Construct byte array of defined type by copying character values from a String. These values - * have to be in the Python byte range 0 to 255. + * Constructs a byte array of defined type by copying character values from a String. These + * values have to be in the Python byte range 0 to 255. * * @param type explicit Jython type * @param value source of characters @@ -250,7 +268,8 @@ * @param encoding name of optional encoding * @param errors name of optional errors policy * @return encoded string - * @throws PyException(TypeError) if the PyString is actually a PyUnicode and encoding is null + * @throws PyException (TypeError) if the PyString is actually a {@link PyUnicode} + * and encoding is null */ protected static String encode(PyString arg, String encoding, String errors) throws PyException { // Jython encode emits a String (not byte[]) @@ -278,7 +297,7 @@ * * @param start index in this byte array at which the first character code lands * @param value source of characters - * @throws PyException(ValueError) if any value[i] > 255 + * @throws PyException (ValueError) if any value[i] > 255 */ protected void setBytes(int start, String value) throws PyException { int n = value.length(); @@ -294,7 +313,7 @@ * * @param start index in this byte array at which the first character code lands * @param value source of characters - * @throws PyException(ValueError) if any value[i] > 255 + * @throws PyException (ValueError) if any value[i] > 255 */ protected void setBytes(int start, int step, String value) throws PyException { int n = value.length(); @@ -307,7 +326,7 @@ /** * Helper for __new__ and __init__ and the Java API constructor from - * int in subclasses. Construct zero-filled bytearray of specified size. + * int in subclasses. Construct zero-filled byte array of specified size. * * @param n size of zero-filled array */ @@ -332,24 +351,11 @@ view.copyTo(storage, offset); } -// /** -// * Helper for the Java API constructor from a {@link #PyBuffer}. View is (perhaps) a stop-gap -// until -// * the Jython implementation of PEP 3118 (buffer API) is embedded. -// * -// * @param value a byte-oriented view -// */ -// void init(PyBuffer value) { -// int n = value.getLen(); -// newStorage(n); -// value.copyTo(storage, offset); -// } -// /** * Helper for __new__ and __init__ and the Java API constructor from - * bytearray or bytes in subclasses. + * bytearray or bytes in subclasses. * - * @param source bytearray (or bytes) to copy + * @param source bytearray (or bytes) to copy */ protected void init(BaseBytes source) { newStorage(source.size); @@ -429,8 +435,8 @@ * Load bytes into the container from the given iterable * * @param iter iterable source of values to enter into the container - * @throws PyException(TypeError) if any value not acceptable type - * @throws PyException(ValueError) if any value<0 or value>255 or string length!=1 + * @throws PyException (TypeError) if any value not acceptable type + * @throws PyException (ValueError) if any value<0 or value>255 or string length!=1 */ void loadFrom(Iterable iter) throws PyException { @@ -535,7 +541,7 @@ * Check that an index is within the range of the array, that is 0<=index<size. * * @param index to check - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ protected final void indexCheck(int index) throws PyException { if (index < 0 || index >= size) { @@ -569,7 +575,7 @@ * 0..255.) * * @param value to convert. - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (ValueError) if value<0 or value>255 */ protected static final byte byteCheck(int value) throws PyException { if (value < 0 || value > 255) { @@ -584,7 +590,7 @@ * Python bytes run 0..255.) * * @param value to convert. - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (ValueError) if value<0 or value>255 */ protected static final byte byteCheck(PyInteger value) throws PyException { return byteCheck(value.asInt()); @@ -602,8 +608,8 @@ * * * @param value to convert. - * @throws PyException(TypeError) if not acceptable type - * @throws PyException(ValueError) if value<0 or value>255 or string length!=1 + * @throws PyException (TypeError) if not acceptable type + * @throws PyException (ValueError) if value<0 or value>255 or string length!=1 */ protected static final byte byteCheck(PyObject value) throws PyException { if (value.isIndex()) { @@ -627,7 +633,7 @@ * if the return value is not null. * * @param b object to wrap - * @return byte-oriented view or null + * @return byte-oriented view or null */ protected static PyBuffer getView(PyObject b) { @@ -673,6 +679,7 @@ * API for org.python.core.PySequence * ============================================================================================ */ + @Override protected PyInteger pyget(int index) { return new PyInteger(intAt(index)); } @@ -681,8 +688,10 @@ * We're not implementing these here, but we can give a stronger guarantee about the return type * and save some casting and type anxiety. */ + @Override protected abstract BaseBytes getslice(int start, int stop, int step); + @Override protected abstract BaseBytes repeat(int count); /* @@ -696,9 +705,9 @@ * * @param index to insert at * @param element to insert (by value) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the subclass is immutable */ public void pyinsert(int index, PyObject element) { // This won't succeed: it just produces the right error. @@ -720,8 +729,8 @@ } /** - * Class defining the behaviour of bytearray with respect to slice assignment, etc., which - * differs from the default (list) behaviour in small ways. + * Class defining the behaviour of bytearray with respect to slice assignment, + * etc., which differs from the default (list) behaviour in small ways. */ private class IndexDelegate extends PySequence.DefaultIndexDelegate { @@ -886,11 +895,11 @@ } /** - * Implementation of __eq__ (equality) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __eq__ (equality) operator, capable of comparison with another byte array. + * Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___eq__(PyObject other) { int cmp = basebytes_cmpeq(other); @@ -904,11 +913,11 @@ } /** - * Implementation of __ne__ (not equals) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __ne__ (not equals) operator, capable of comparison with another byte + * array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___ne__(PyObject other) { int cmp = basebytes_cmpeq(other); @@ -922,11 +931,11 @@ } /** - * Implementation of __lt__ (less than) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __lt__ (less than) operator, capable of comparison with another byte array. + * Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___lt__(PyObject other) { int cmp = basebytes_cmp(other); @@ -941,10 +950,10 @@ /** * Implementation of __le__ (less than or equal to) operator, capable of comparison with another - * byte array or bytes. Comparison with an invalid type returns null. + * byte array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___le__(PyObject other) { int cmp = basebytes_cmp(other); @@ -959,10 +968,10 @@ /** * Implementation of __ge__ (greater than or equal to) operator, capable of comparison with - * another byte array or bytes. Comparison with an invalid type returns null. + * another byte array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___ge__(PyObject other) { int cmp = basebytes_cmp(other); @@ -977,10 +986,10 @@ /** * Implementation of __gt__ (greater than) operator, capable of comparison with another byte - * array or bytes. Comparison with an invalid type returns null. + * array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___gt__(PyObject other) { int cmp = basebytes_cmp(other); @@ -1031,7 +1040,7 @@ * @param ostart of slice to search. * @param oend of slice to search. * @param endswith true if we are doing endswith, false if startswith. - * @return true if and only if this bytearray ends with (one of) target. + * @return true if and only if this byte array ends with (one of) target. */ protected final synchronized boolean basebytes_starts_or_endswith(PyObject target, PyObject ostart, PyObject oend, boolean endswith) { @@ -1151,7 +1160,7 @@ * error policy. The returned PyObject will usually be a PyUnicode, but in practice * it is whatever the decode method of the codec decides. * - * @param encoding the name of the codec (uses default codec if null) + * @param encoding the name of the codec (uses default codec if null) * @return object containing the decoded characters */ public PyObject decode(String encoding) { @@ -1163,8 +1172,8 @@ * policy. The returned PyObject will usually be a PyUnicode, but in practice it is * whatever the decode method of the codec decides. * - * @param encoding the name of the codec (uses default codec if null) - * @param errors the name of the error policy (uses 'strict' if null) + * @param encoding the name of the codec (uses default codec if null) + * @param errors the name of the error policy (uses 'strict' if null) * @return object containing the decoded characters */ public PyObject decode(String encoding, String errors) { @@ -1210,6 +1219,7 @@ * * @return PyTuple that is first stage in pickling byte array */ + @Override public PyObject __reduce__() { return basebytes___reduce__(); } @@ -1555,6 +1565,7 @@ * the table is only 32 elements long, rather than (as it might be) 256 bytes, the number of * distinct byte values. */ + @Override protected int[] calculateSkipTable() { int[] skipTable = new int[MASK + 1]; int m = pattern.getLen(); @@ -1572,6 +1583,7 @@ * * @return the new effective end of the text */ + @Override public int currIndex() { return right + pattern.getLen() - 1; } @@ -1583,6 +1595,7 @@ * * @return matching index or -1 if no (further) occurrences found */ + @Override public int nextIndex() { int m = pattern.getLen(); @@ -1855,7 +1868,7 @@ * * @param result to receive the decoded values * @param hex specification of the bytes - * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered + * @throws PyException (ValueError) if non-hex characters, or isolated ones, are encountered */ static void basebytes_fromhex(BaseBytes result, String hex) throws PyException { @@ -1937,7 +1950,7 @@ // Unsuitable object to be in this join String fmt = "can only join an iterable of bytes (item %d has type '%.80s')"; throw Py.TypeError(String.format(fmt, iterList.size(), o.getType() - .fastGetName())); + .fastGetName())); } iterList.add(v); totalSize += v.getLen(); @@ -1990,6 +2003,9 @@ * the part before the separator, the separator itself, and the part after the separator. If the * separator is not found, return a 3-tuple containing the string itself, followed by two empty * byte arrays. + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2000,6 +2016,9 @@ /** * Ready-to-expose implementation of Python partition(sep). + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2032,6 +2051,9 @@ /** * Construct return value for implementation of Python partition(sep) or * rpartition(sep), returns [0:p], [p:q], [q:] + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param p start of separator * @param q start of tail @@ -2430,6 +2452,9 @@ * containing the part before the separator, the separator itself, and the part after the * separator. If the separator is not found, return a 3-tuple containing two empty byte arrays, * followed by the byte array itself. + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2440,6 +2465,9 @@ /** * Ready-to-expose implementation of Python rpartition(sep). + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2471,6 +2499,9 @@ /** * Implementation of Python rsplit(), that returns a list of the words in the byte * array, using whitespace as the delimiter. See {@link #rsplit(PyObject, int)}. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return PyList of byte arrays that result from the split */ @@ -2480,10 +2511,13 @@ /** * Implementation of Python rsplit(sep), that returns a list of the words in the - * byte array, using sep as the delimiter. See {@link #rsplit(PyObject, int)} for the semantics - * of the separator. + * byte array, using sep as the delimiter. See {@link #rsplit(PyObject, int)} for + * the semantics of the separator. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @return PyList of byte arrays that result from the split */ public PyList rsplit(PyObject sep) { @@ -2492,22 +2526,26 @@ /** * Implementation of Python rsplit(sep, maxsplit), that returns a list of the words - * in the byte array, using sep as the delimiter. If maxsplit is given, at most maxsplit splits - * are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep as the delimiter. If maxsplit is + * given, at most maxsplit splits are done (thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is + * no limit on the number of splits (all possible splits are made). *

- * The semantics of sep and maxcount are identical to those of split(sep, maxsplit) - * , except that splits are generated from the right (and pushed onto the front of the result - * list). The result is only different from that of split if maxcount - * limits the number of splits. For example, + * The semantics of sep and maxcount are identical to those of + * split(sep, maxsplit) , except that splits are generated from the right (and + * pushed onto the front of the result list). The result is only different from that of + * split if maxcount limits the number of splits. For example, *

    *
  • bytearray(b' 1 2 3 ').rsplit() returns * [bytearray(b'1'), bytearray(b'2'), bytearray(b'3')], and
  • *
  • bytearray(b' 1 2 3 ').rsplit(None, 1) returns * [bytearray(b' 1 2'), bytearray(b'3')]
  • . *
+ *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2517,10 +2555,13 @@ /** * Ready-to-expose implementation of Python rsplit(sep, maxsplit), that returns a - * list of the words in the byte array, using sep as the delimiter. Use the defines whitespace - * semantics if sep is null. + * list of the words in the byte array, using sep as the delimiter. Use the defines + * whitespace semantics if sep is null. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2534,11 +2575,15 @@ /** * Implementation of Python rsplit(sep, maxsplit), that returns a list of the words - * in the byte array, using sep (which is not null) as the delimiter. If maxsplit>=0, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If - * maxsplit<0, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep (which is not null) as the delimiter. + * If maxsplit>=0, at most maxsplit splits are done (thus, the list + * will have at most maxsplit+1 elements). If maxsplit<0, then + * there is no limit on the number of splits (all possible splits are made). + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2589,15 +2634,18 @@ /** * Implementation of Python rsplit(None, maxsplit), that returns a list of the - * words in the byte array, using whitespace as the delimiter. If maxsplit is given, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit - * is not specified, then there is no limit on the number of splits (all possible splits are - * made). + * words in the byte array, using whitespace as the delimiter. If maxsplit is + * given, at most maxsplit splits are done (thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is no limit on + * the number of splits (all possible splits are made). *

* Runs of consecutive whitespace are regarded as a single separator, and the result will * contain no empty strings at the start or end if the string has leading or trailing * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace - * with a None separator returns []. + * with a None separator returns []. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this/self. * * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split @@ -2648,6 +2696,9 @@ /** * Implementation of Python split(), that returns a list of the words in the byte * array, using whitespace as the delimiter. See {@link #split(PyObject, int)}. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return PyList of byte arrays that result from the split */ @@ -2657,10 +2708,13 @@ /** * Implementation of Python split(sep), that returns a list of the words in the - * byte array, using sep as the delimiter. See {@link #split(PyObject, int)} for the semantics - * of the separator. + * byte array, using sep as the delimiter. See {@link #split(PyObject, int)} for + * the semantics of the separator. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @return PyList of byte arrays that result from the split */ public PyList split(PyObject sep) { @@ -2669,29 +2723,33 @@ /** * Implementation of Python split(sep, maxsplit), that returns a list of the words - * in the byte array, using sep as the delimiter. If maxsplit is given, at most maxsplit splits - * are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep as the delimiter. If maxsplit is + * given, at most maxsplit splits are done. (Thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is + * no limit on the number of splits (all possible splits are made). *

- * If sep is given, consecutive delimiters are not grouped together and are deemed to delimit - * empty strings (for example, '1,,2'.split(',') returns ['1', '', '2']). The sep argument may - * consist of multiple characters (for example, '1<>2<>3'.split('<>') returns ['1', - * '2', '3']). Splitting an empty string with a specified separator returns ['']. + * If sep is given, consecutive delimiters are not grouped together and are deemed + * to delimit empty strings (for example, '1,,2'.split(',') returns + * ['1', '', '2']). The sep argument may consist of multiple + * characters (for example, '1<>2<>3'.split('<>') returns ['1', + * '2', '3']). Splitting an empty string with a specified separator ['']. *

- * If sep is not specified or is None, a different splitting algorithm is applied: runs of - * consecutive whitespace are regarded as a single separator, and the result will contain no - * empty strings at the start or end if the string has leading or trailing whitespace. - * Consequently, splitting an empty string or a string consisting of just whitespace with a None - * separator returns []. For example, - * + * If sep is not specified or is None, a different splitting algorithm + * is applied: runs of consecutive whitespace are regarded as a single separator, and the result + * will contain no empty strings at the start or end if the string has leading or trailing + * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace + * with a None separator returns []. For example, *

    *
  • bytearray(b' 1 2 3 ').split() returns * [bytearray(b'1'), bytearray(b'2'), bytearray(b'3')], and
  • *
  • bytearray(b' 1 2 3 ').split(None, 1) returns * [bytearray(b'1'), bytearray(b'2 3 ')].
  • *
+ *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2701,10 +2759,13 @@ /** * Ready-to-expose implementation of Python split(sep, maxsplit), that returns a - * list of the words in the byte array, using sep as the delimiter. Use the defines whitespace - * semantics if sep is null. + * list of the words in the byte array, using sep as the delimiter. Use the defines + * whitespace semantics if sep is null. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2718,11 +2779,15 @@ /** * Implementation of Python split(sep, maxsplit), that returns a list of the words - * in the byte array, using sep (which is not null) as the delimiter. If maxsplit>=0, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If - * maxsplit<0, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep (which is not null) as the delimiter. + * If maxsplit>=0, at most maxsplit splits are done (thus, the list + * will have at most maxsplit+1 elements). If maxsplit<0, then + * there is no limit on the number of splits (all possible splits are made). + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2763,14 +2828,18 @@ /** * Implementation of Python split(None, maxsplit), that returns a list of the words - * in the byte array, using whitespace as the delimiter. If maxsplit is given, at most maxsplit - * splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using whitespace as the delimiter. If maxsplit is given, at + * most maxsplit splits are done (thus, the list will have at most maxsplit+1 + * elements). If maxsplit is not specified, then there is no limit on the number of + * splits (all possible splits are made). *

* Runs of consecutive whitespace are regarded as a single separator, and the result will * contain no empty strings at the start or end if the string has leading or trailing * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace - * with a None separator returns []. + * with a None separator returns []. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split @@ -2815,6 +2884,9 @@ /** * Implementation of Python splitlines(), returning a list of the lines in the byte * array, breaking at line boundaries. Line breaks are not included in the resulting segments. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return List of segments */ @@ -2826,6 +2898,9 @@ * Implementation of Python splitlines(keepends), returning a list of the lines in * the string, breaking at line boundaries. Line breaks are not included in the resulting list * unless keepends is true. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param keepends if true, include the end of line bytes(s) * @return PyList of segments @@ -2838,6 +2913,9 @@ * Ready-to-expose implementation of Python splitlines(keepends), returning a list * of the lines in the array, breaking at line boundaries. Line breaks are not included in the * resulting list unless keepends is given and true. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param keepends if true, include the end of line bytes(s) * @return List of segments @@ -2888,7 +2966,7 @@ * byte. * * @param function name - * @param fillchar or null + * @param fillchar or null * @return */ protected static byte fillByteCheck(String function, String fillchar) { @@ -2937,7 +3015,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_center(int width, String fillchar) { @@ -2958,7 +3036,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_ljust(int width, String fillchar) { @@ -2977,7 +3055,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_rjust(int width, String fillchar) { @@ -3323,7 +3401,8 @@ /** * Java API equivalent of Python capitalize(). This method treats the bytes as * Unicode pont codes and is consistent with Java's {@link Character#toUpperCase(char)} and - * {@link Character#toLowerCase(char)}. + * {@link Character#toLowerCase(char)}. The BaseBytes returned by this method has + * the same actual type as this/self. * * @return a copy of the array with its first character capitalized and the rest lowercased. */ @@ -3332,7 +3411,9 @@ } /** - * Ready-to-expose implementation of Python capitalize(). + * Ready-to-expose implementation of Python capitalize(). The + * BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with its first character capitalized and the rest lowercased. */ @@ -3365,7 +3446,9 @@ /** * Java API equivalent of Python lower(). This method treats the bytes as Unicode - * pont codes and is consistent with Java's {@link Character#toLowerCase(char)}. + * pont codes and is consistent with Java's {@link Character#toLowerCase(char)}. The + * BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with all the cased characters converted to lowercase. */ @@ -3374,7 +3457,8 @@ } /** - * Ready-to-expose implementation of Python lower(). + * Ready-to-expose implementation of Python lower(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with all the cased characters converted to lowercase. */ @@ -3397,7 +3481,8 @@ /** * Java API equivalent of Python swapcase(). This method treats the bytes as * Unicode pont codes and is consistent with Java's {@link Character#toUpperCase(char)} and - * {@link Character#toLowerCase(char)}. + * {@link Character#toLowerCase(char)}. The BaseBytes returned by this method has + * the same actual type as this/self. * * @return a copy of the array with uppercase characters converted to lowercase and vice versa. */ @@ -3406,7 +3491,8 @@ } /** - * Ready-to-expose implementation of Python swapcase(). + * Ready-to-expose implementation of Python swapcase(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with uppercase characters converted to lowercase and vice versa. */ @@ -3432,7 +3518,8 @@ * Java API equivalent of Python title(). The algorithm uses a simple * language-independent definition of a word as groups of consecutive letters. The definition * works in many contexts but it means that apostrophes in contractions and possessives form - * word boundaries, which may not be the desired result. + * word boundaries, which may not be the desired result. The BaseBytes returned by + * this method has the same actual type as this/self. * * @return a titlecased version of the array where words start with an uppercase character and * the remaining characters are lowercase. @@ -3442,7 +3529,8 @@ } /** - * Ready-to-expose implementation of Python title(). + * Ready-to-expose implementation of Python title(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a titlecased version of the array where words start with an uppercase character and * the remaining characters are lowercase. @@ -3481,7 +3569,8 @@ /** * Java API equivalent of Python upper(). Note that * x.upper().isupper() might be false if the array contains uncased - * characters. + * characters. The BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with all the cased characters converted to uppercase. */ @@ -3490,7 +3579,8 @@ } /** - * Ready-to-expose implementation of Python upper(). + * Ready-to-expose implementation of Python upper(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with all the cased characters converted to uppercase. */ @@ -3533,7 +3623,7 @@ * * @param index of value in byte array * @return the integer value at the index - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ public synchronized int intAt(int index) throws PyException { indexCheck(index); @@ -3599,7 +3689,7 @@ */ private static final void appendHexEscape(StringBuilder buf, int c) { buf.append("\\x").append(Character.forDigit((c & 0xf0) >> 4, 16)) - .append(Character.forDigit(c & 0xf, 16)); + .append(Character.forDigit(c & 0xf, 16)); } /** @@ -3666,8 +3756,8 @@ */ /** - * Access to the bytearray (or bytes) as a {@link java.util.List}. The List interface supplied - * by BaseBytes delegates to this object. + * Access to the byte array as a {@link java.util.List}. The List interface supplied by + * BaseBytes delegates to this object. */ protected final List listDelegate = new AbstractList() { @@ -3689,9 +3779,9 @@ * Replaces the element at the specified position in this list with the specified element. * * @see java.util.AbstractList#set(int, java.lang.Object) - * @throws PyException(TypeError) if actual class is immutable - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if actual class is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 */ @Override public PyInteger set(int index, PyInteger element) throws PyException { @@ -3707,9 +3797,9 @@ * currently at that position and any subsequent elements to the right. * * @see java.util.AbstractList#add(int, java.lang.Object) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the owning concrete subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the owning concrete subclass is immutable */ @Override public void add(int index, PyInteger element) throws PyException { @@ -3724,7 +3814,7 @@ * removed from the list. * * @see java.util.AbstractList#remove(int) - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ @Override public PyInteger remove(int index) { @@ -3737,11 +3827,12 @@ }; /** - * Number of bytes in bytearray (or bytes) object. + * Number of bytes in bytearray (or bytes) object. * * @see java.util.List#size() * @return Number of bytes in byte array. * */ + @Override public int size() { return size; } @@ -3749,6 +3840,7 @@ /* * @see java.util.List#isEmpty() */ + @Override public boolean isEmpty() { return size == 0; } @@ -3757,6 +3849,7 @@ * Returns true if this list contains the specified value. More formally, returns true if and * only if this list contains at least one integer e such that o.equals(PyInteger(e)). */ + @Override public boolean contains(Object o) { return listDelegate.contains(o); } @@ -3764,6 +3857,7 @@ /* * @see java.util.List#iterator() */ + @Override public Iterator iterator() { return listDelegate.iterator(); } @@ -3771,6 +3865,7 @@ /* * @see java.util.List#toArray() */ + @Override public Object[] toArray() { return listDelegate.toArray(); } @@ -3778,6 +3873,7 @@ /* * @see java.util.List#toArray(T[]) */ + @Override public T[] toArray(T[] a) { return listDelegate.toArray(a); } @@ -3785,6 +3881,7 @@ /* * @see java.util.List#add(java.lang.Object) */ + @Override public boolean add(PyInteger o) { return listDelegate.add(o); } @@ -3792,6 +3889,7 @@ /* * @see java.util.List#remove(java.lang.Object) */ + @Override public boolean remove(Object o) { return listDelegate.remove(o); } @@ -3799,6 +3897,7 @@ /* * @see java.util.List#containsAll(java.util.Collection) */ + @Override public boolean containsAll(Collection c) { return listDelegate.containsAll(c); } @@ -3806,6 +3905,7 @@ /* * @see java.util.List#addAll(java.util.Collection) */ + @Override public boolean addAll(Collection c) { return listDelegate.addAll(c); } @@ -3813,6 +3913,7 @@ /* * @see java.util.List#addAll(int, java.util.Collection) */ + @Override public boolean addAll(int index, Collection c) { return listDelegate.addAll(index, c); } @@ -3820,6 +3921,7 @@ /* * @see java.util.List#removeAll(java.util.Collection) */ + @Override public boolean removeAll(Collection c) { return listDelegate.removeAll(c); } @@ -3827,6 +3929,7 @@ /* * @see java.util.List#retainAll(java.util.Collection) */ + @Override public boolean retainAll(Collection c) { return listDelegate.retainAll(c); } @@ -3834,6 +3937,7 @@ /* * @see java.util.List#clear() */ + @Override public void clear() { listDelegate.clear(); } @@ -3865,6 +3969,7 @@ /* * @see java.util.List#hashCode() */ + @Override public int hashCode() { return listDelegate.hashCode(); } @@ -3872,6 +3977,7 @@ /* * @see java.util.List#get(int) */ + @Override public PyInteger get(int index) { return listDelegate.get(index); } @@ -3879,6 +3985,7 @@ /* * @see java.util.List#set(int, java.lang.Object) */ + @Override public PyInteger set(int index, PyInteger element) { return listDelegate.set(index, element); } @@ -3886,6 +3993,7 @@ /* * @see java.util.List#add(int, java.lang.Object) */ + @Override public void add(int index, PyInteger element) { listDelegate.add(index, element); } @@ -3893,6 +4001,7 @@ /* * @see java.util.List#remove(int) */ + @Override public PyInteger remove(int index) { return listDelegate.remove(index); } @@ -3900,6 +4009,7 @@ /* * @see java.util.List#indexOf(java.lang.Object) */ + @Override public int indexOf(Object o) { return listDelegate.indexOf(o); } @@ -3907,6 +4017,7 @@ /* * @see java.util.List#lastIndexOf(java.lang.Object) */ + @Override public int lastIndexOf(Object o) { return listDelegate.lastIndexOf(o); } @@ -3914,6 +4025,7 @@ /* * @see java.util.List#listIterator() */ + @Override public ListIterator listIterator() { return listDelegate.listIterator(); } @@ -3921,6 +4033,7 @@ /* * @see java.util.List#listIterator(int) */ + @Override public ListIterator listIterator(int index) { return listDelegate.listIterator(index); } @@ -3928,6 +4041,7 @@ /* * @see java.util.List#subList(int, int) */ + @Override public List subList(int fromIndex, int toIndex) { return listDelegate.subList(fromIndex, toIndex); } @@ -3986,8 +4100,8 @@ * It is intended the client call this method only once to get the result of a series of * append operations. A second call to {@link #getCount()}, before any further appending, * returns a zero-length array. This is to ensure that the same array is not given out - * twice. However, {@link #getCount()} continues to return the number bytes accumuated until - * an append next occurs. + * twice. However, {@link #getCount()} continues to return the number bytes accumulated + * until an append next occurs. * * @return an array containing the accumulated result */ @@ -4131,7 +4245,7 @@ * Every sub-class of BaseBytes overrides this method to return a Builder<B> * where B is (normally) that class's particular type, and it extends * Builder<B> so that {@link Builder#getResult()} produces an instance of - * B from the contents.. + * B from the contents. * * @param capacity of the Builder<B> returned * @return a Builder<B> for the correct sub-class diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -12,26 +12,24 @@ import org.python.expose.MethodType; /** - * Partial implementation of Python bytearray. At the present stage of development, the class - * provides: - *

    - *
  • constructors (both __init__ and the Java API constructors),
  • - *
  • the slice operations (get, set and delete)
  • - *
  • a List<PyInteger> implementation for the Java API
  • - *
- * and this is founded on a particular approach to storage management internally. However, the - * implementation does not support the memoryview interface either for access or a a - * source for its constructors although the signatures are present. The rich set of string-like - * operations due a bytearray is not implemented. - * + * Implementation of Python bytearray with a Java API that includes equivalents to most + * of the Python API. These Python equivalents accept a {@link PyObject} as argument, where you + * might have expected a byte[] or PyByteArray, in order to accommodate + * the full range of types accepted by the Python equivalent: usually, any PyObject + * that implements {@link BufferProtocol}, providing a one-dimensional array of bytes, is an + * acceptable argument. In the documentation, the reader will often see the terms "byte array" or + * "object viewable as bytes" instead of bytearray when this broader scope is intended. + * This may relate to parameters, or to the target object itself (in text that applies equally to + * base or sibling classes). */ @ExposedType(name = "bytearray", base = PyObject.class, doc = BuiltinDocs.bytearray_doc) public class PyByteArray extends BaseBytes implements BufferProtocol { + /** The {@link PyType} of bytearray. */ public static final PyType TYPE = PyType.fromClass(PyByteArray.class); /** - * Create a zero-length Python bytearray of explicitly-specified sub-type + * Constructs a zero-length Python bytearray of explicitly-specified sub-type * * @param type explicit Jython type */ @@ -40,16 +38,16 @@ } /** - * Create a zero-length Python bytearray. + * Constructs a zero-length Python bytearray. */ public PyByteArray() { super(TYPE); } /** - * Create zero-filled Python bytearray of specified size. + * Constructs zero-filled Python bytearray of specified size. * - * @param size of bytearray + * @param size of bytearray */ public PyByteArray(int size) { super(TYPE); @@ -57,7 +55,7 @@ } /** - * Construct bytearray by copying values from int[]. + * Constructs a bytearray by copying values from int[]. * * @param value source of the bytes (and size) */ @@ -66,8 +64,8 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is a - * bytearray (or bytes). + * Constructs a new array filled exactly by a copy of the contents of the source, which is a + * bytearray (or bytes). * * @param value source of the bytes (and size) */ @@ -77,7 +75,7 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is a + * Constructs a new array filled exactly by a copy of the contents of the source, which is a * byte-oriented {@link PyBuffer}. * * @param value source of the bytes (and size) @@ -88,7 +86,7 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is an + * Constructs a new array filled exactly by a copy of the contents of the source, which is an * object supporting the Jython version of the PEP 3118 buffer API. * * @param value source of the bytes (and size) @@ -99,7 +97,7 @@ } /** - * Create a new array filled from an iterable of PyObject. The iterable must yield objects + * Constructs a new array filled from an iterable of PyObject. The iterable must yield objects * convertible to Python bytes (non-negative integers less than 256 or strings of length 1). * * @param value source of the bytes (and size) @@ -110,8 +108,8 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, the encoding must be explicitly specified. * * @param arg primary argument from which value is taken * @param encoding name of optional encoding (must be a string type) @@ -123,12 +121,12 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, the encoding must be explicitly specified. * * @param arg primary argument from which value is taken - * @param encoding name of optional encoding (may be null to select the default for this - * installation) + * @param encoding name of optional encoding (may be null to select the default for + * this installation) * @param errors name of optional errors policy */ public PyByteArray(PyString arg, String encoding, String errors) { @@ -137,8 +135,8 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, an exception is thrown saying that the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, an exception is thrown saying that the encoding must be explicitly specified. * * @param arg primary argument from which value is taken */ @@ -148,7 +146,8 @@ } /** - * Construct bytearray by re-using an array of byte as storage initialised by the client. + * Constructs a bytearray by re-using an array of byte as storage initialised by + * the client. * * @param storage pre-initialised with desired value: the caller should not keep a reference */ @@ -158,12 +157,13 @@ } /** - * Construct bytearray by re-using an array of byte as storage initialised by the client. + * Constructs a bytearray by re-using an array of byte as storage initialised by + * the client. * * @param storage pre-initialised with desired value: the caller should not keep a reference * @param size number of bytes actually used - * @throws IllegalArgumentException if the range [0:size] is not within the array bounds of - * the storage. + * @throws IllegalArgumentException if the range [0:size] is not within the array bounds of the + * storage. */ PyByteArray(byte[] storage, int size) { super(TYPE); @@ -171,28 +171,26 @@ } /** - * Create a new bytearray object from an arbitrary Python object according to the same rules as - * apply in Python to the bytearray() constructor: + * Constructs a new bytearray object from an arbitrary Python object according to + * the same rules as apply in Python to the bytearray() constructor: *
    - *
  • bytearray() Construct a zero-length bytearray (arg is null).
  • - *
  • bytearray(int) Construct a zero-initialized bytearray of the given length.
  • - *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in [0..255]
  • - *
  • bytearray(string [, encoding [, errors] ]) Construct from a text string, optionally using - * the specified encoding.
  • - *
  • bytearray(unicode, encoding [, errors]) Construct from a unicode string using the - * specified encoding.
  • - *
  • bytearray(bytes_or_bytearray) Construct as a mutable copy of bytes or existing bytearray - * object.
  • + *
  • bytearray() Construct a zero-length bytearray.
  • + *
  • bytearray(int) Construct a zero-initialized bytearray of the + * given length.
  • + *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in + * [0..255]
  • + *
  • bytearray(buffer) Construct by reading from any object implementing + * {@link BufferProtocol}, including str/bytes or another bytearray.
  • *
* When it is necessary to specify an encoding, as in the Python signature - * bytearray(string, encoding[, errors]), use the constructor - * {@link #PyByteArray(PyString, String, String)}. If the PyString is actually a PyUnicode, an - * encoding must be specified, and using this constructor will throw an exception about that. + * bytearray(string, encoding [, errors]), use the constructor + * {@link #PyByteArray(PyString, String, String)}. If the PyString is actually a + * PyUnicode, an encoding must be specified, and using this constructor will throw + * an exception about that. * - * @param arg primary argument from which value is taken (may be null) - * @throws PyException in the same circumstances as bytearray(arg), TypeError for non-iterable, - * non-integer argument type, and ValueError if iterables do not yield byte [0..255] - * values. + * @param arg primary argument from which value is taken (may be null) + * @throws PyException (TypeError) for non-iterable, + * @throws PyException (ValueError) if iterables do not yield byte [0..255] values. */ public PyByteArray(PyObject arg) throws PyException { super(TYPE); @@ -284,18 +282,19 @@ } } - /* ============================================================================================ + /* + * ============================================================================================ * API for org.python.core.PySequence * ============================================================================================ */ /** - * Returns a slice of elements from this sequence as a PyByteArray. + * Returns a slice of elements from this sequence as a PyByteArray. * * @param start the position of the first element. * @param stop one more than the position of the last element. * @param step the step size. - * @return a PyByteArray corresponding the the given range of elements. + * @return a PyByteArray corresponding the the given range of elements. */ @Override protected synchronized PyByteArray getslice(int start, int stop, int step) { @@ -333,8 +332,8 @@ } /** - * Returns a PyByteArray that repeats this sequence the given number of times, as in the - * implementation of __mul__ for strings. + * Returns a PyByteArray that repeats this sequence the given number of times, as + * in the implementation of __mul__ for strings. * * @param count the number of times to repeat this. * @return this byte array repeated count times. @@ -347,8 +346,8 @@ } /** - * Replace the contents of this PyByteArray with the given number of repeats of the original - * contents, as in the implementation of __mul__ for strings. + * Replace the contents of this PyByteArray with the given number of repeats of the + * original contents, as in the implementation of __mul__ for strings. * * @param count the number of times to repeat this. */ @@ -357,35 +356,36 @@ } /** - * Sets the indexed element of the bytearray to the given value. This is an extension point - * called by PySequence in its implementation of {@link #__setitem__} It is guaranteed by - * PySequence that the index is within the bounds of the array. Any other clients calling - * pyset(int) must make the same guarantee. + * Sets the indexed element of the bytearray to the given value. This is an + * extension point called by PySequence in its implementation of {@link #__setitem__} It is + * guaranteed by PySequence that the index is within the bounds of the array. Any other clients + * calling pyset(int) must make the same guarantee. * * @param index index of the element to set. * @param value the value to set this element to. - * @throws PyException(AttributeError) if value cannot be converted to an integer - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (AttributeError) if value cannot be converted to an integer + * @throws PyException (ValueError) if value<0 or value>255 */ + @Override public synchronized void pyset(int index, PyObject value) throws PyException { storage[index + offset] = byteCheck(value); } /** - * Insert the element (interpreted as a Python byte value) at the given index. - * Python int, long and string types of length 1 are allowed. + * Insert the element (interpreted as a Python byte value) at the given index. Python + * int, long and str types of length 1 are allowed. * * @param index to insert at * @param element to insert (by value) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the subclass is immutable */ @Override public synchronized void pyinsert(int index, PyObject element) { // Open a space at the right location. storageReplace(index, 0, 1); - storage[offset+index] = byteCheck(element); + storage[offset + index] = byteCheck(element); } /** @@ -398,9 +398,10 @@ * of exactly that many elements (or convertible to such a sequence). *

* When assigning from a sequence type or iterator, the sequence may contain arbitrary - * PyObjects, but acceptable ones are PyInteger, PyLong or PyString of length 1. If - * any one of them proves unsuitable for assignment to a Python bytarray element, an exception - * is thrown and this bytearray is unchanged. + * {@link PyObject}s, but acceptable ones are {@link PyInteger}, {@link PyLong} or + * {@link PyString} of length 1. If any one of them proves unsuitable for assignment to a Python + * bytearray element, an exception is thrown and this bytearray is + * unchanged. * *

      * a = bytearray(b'abcdefghijklmnopqrst')
@@ -472,14 +473,14 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * zero-filled bytearray of the given length.
+     * zero-filled bytearray of the given length.
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param len number of zeros to insert consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
      */
     private void setslice(int start, int stop, int step, int len) throws PyException {
         if (step == 1) {
@@ -501,14 +502,14 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * PyString.
+     * {@link PyString}.
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param value a PyString object consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
      */
     private void setslice(int start, int stop, int step, PyString value) throws PyException {
         String v = value.asString();
@@ -536,7 +537,7 @@
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param value an object supporting the buffer API consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
      */
     private void setslice(int start, int stop, int step, BufferProtocol value) throws PyException {
 
@@ -547,7 +548,7 @@
         if (step == 1) {
             // Delete this[start:stop] and open a space of the right size
             storageReplace(start, stop - start, len);
-            view.copyTo(storage, start+offset);
+            view.copyTo(storage, start + offset);
 
         } else {
             // This is an extended slice which means we are replacing elements
@@ -564,14 +565,14 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * bytearray (or bytes).
+     * bytearray (or bytes).
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
-     * @param value a bytearray (or bytes) object consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @param value a bytearray (or bytes) object consistent with the slice assignment
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
      */
     private void setslice(int start, int stop, int step, BaseBytes value) throws PyException {
 
@@ -603,14 +604,14 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * bytearray (or bytes).
+     * bytearray (or bytes).
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param iter iterable source of values to enter in the array
-     * @throws PyException(SliceSizeError) if the iterable size is inconsistent with an extended
+     * @throws PyException (SliceSizeError) if the iterable size is inconsistent with an extended
      *             slice
      */
     private void setslice(int start, int stop, int step, Iterable iter) {
@@ -640,21 +641,12 @@
         }
     }
 
-// Idiom:
-// if (step == 1) {
-// // Do something efficient with block start...stop-1
-// } else {
-// int n = sliceLength(start, stop, step);
-// for (int i = start, j = 0; j < n; i += step, j++) {
-// // Perform jth operation with element i
-// }
-// }
-
     /*
      * Deletes an element from the sequence (and closes up the gap).
      *
      * @param index index of the element to delete.
      */
+    @Override
     protected synchronized void del(int index) {
         // XXX Change SequenceIndexDelegate to avoid repeated calls to del(int) for extended slice
         storageDelete(index, 1);
@@ -667,6 +659,7 @@
      *
      * @param stop one more than the position of the last element.
      */
+    @Override
     protected synchronized void delRange(int start, int stop) {
         storageDelete(start, stop - start);
     }
@@ -714,29 +707,29 @@
     }
 
     /**
-     * Initialise a mutable bytearray object from various arguments. This single initialisation must
-     * support:
+     * Initialise a mutable bytearray object from various arguments. This single
+     * initialisation must support:
      * 
    - *
  • bytearray() Construct a zero-length bytearray.
  • - *
  • bytearray(int) Construct a zero-initialized bytearray of the given length.
  • - *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in [0..255]
  • - *
  • bytearray(string [, encoding [, errors] ]) Construct from a text string, optionally using + *
  • bytearray() Construct a zero-length bytearray.
  • + *
  • bytearray(int) Construct a zero-initialized bytearray of the + * given length.
  • + *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in + * [0..255]
  • + *
  • bytearray(buffer) Construct by reading from any object implementing + * {@link BufferProtocol}, including str/bytes or another bytearray.
  • + *
  • bytearray(string, encoding [, errors]) Construct from a + * str/bytes, decoded using the system default encoding, and encoded to bytes using * the specified encoding.
  • - *
  • bytearray(unicode, encoding [, errors]) Construct from a unicode string using the - * specified encoding.
  • - *
  • bytearray(bytes_or_bytearray) Construct as a mutable copy of bytes or existing bytearray - * object.
  • + *
  • bytearray(unicode, encoding [, errors]) Construct from a + * unicode string, encoded to bytes using the specified encoding.
  • *
- * Unlike CPython we are not able to support the initialisation:
  • bytearray(memory_view) - * Construct as copy of any object implementing the buffer API.
  • Although effectively - * a constructor, it is possible to call __init__ on a 'used' object so the method does not - * assume any particular prior state. + * Although effectively a constructor, it is possible to call __init__ on a 'used' + * object so the method does not assume any particular prior state. * * @param args argument array according to Jython conventions * @param kwds Keywords according to Jython conventions - * @throws PyException in the same circumstances as bytearray(arg), TypeError for non-iterable, - * non-integer argument type, and ValueError if iterables do not yield byte [0..255] - * values. + * @throws PyException (TypeError) for non-iterable, + * @throws PyException (ValueError) if iterables do not yield byte [0..255] values. */ @ExposedNew @ExposedMethod(doc = BuiltinDocs.bytearray___init___doc) @@ -795,7 +788,8 @@ }; } - /* ============================================================================================ + /* + * ============================================================================================ * Python API rich comparison operations * ============================================================================================ */ @@ -830,8 +824,6 @@ return basebytes___gt__(other); } - - @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.bytearray___eq___doc) final synchronized PyObject bytearray___eq__(PyObject other) { return basebytes___eq__(other); @@ -862,7 +854,8 @@ return basebytes___gt__(other); } -/* ============================================================================================ +/* + * ============================================================================================ * Python API for bytearray * ============================================================================================ */ @@ -876,10 +869,8 @@ final synchronized PyObject bytearray___add__(PyObject o) { PyByteArray sum = null; - // XXX re-write using buffer API - if (o instanceof BaseBytes) { BaseBytes ob = (BaseBytes)o; // Quick route: allocate the right size bytearray and copy the two parts in. @@ -985,7 +976,7 @@ * length 1. * * @param element the item to append. - * @throws PyException(ValueError) if element<0 or element>255 + * @throws PyException (ValueError) if element<0 or element>255 */ public void append(PyObject element) { bytearray_append(element); @@ -1002,9 +993,10 @@ * Implement to the standard Python __contains__ method, which in turn implements the * in operator. * - * @param o the element to search for in this bytearray. + * @param o the element to search for in this bytearray. * @return the result of the search. **/ + @Override public boolean __contains__(PyObject o) { return basebytes___contains__(o); } @@ -1038,7 +1030,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray center(int width, String fillchar) { @@ -1097,11 +1089,11 @@ * Implementation of Python endswith(suffix). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @return true if and only if this bytearray ends with the suffix (or one of them) + * @return true if and only if this bytearray ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix) { return basebytes_starts_or_endswith(suffix, null, null, true); @@ -1111,13 +1103,14 @@ * Implementation of Python endswith( suffix [, start ] ). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. With optional - * start (which may be null or Py.None), define the - * effective bytearray to be the slice [start:] of this bytearray. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. With + * optional start (which may be null or Py.None), define + * the effective bytearray to be the slice [start:] of this + * bytearray. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match + * @param start of slice in this bytearray to match * @return true if and only if this[start:] ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix, PyObject start) { @@ -1128,15 +1121,15 @@ * Implementation of Python endswith( suffix [, start [, end ]] ). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. With optional - * start and end (which may be null or - * Py.None), define the effective bytearray to be the slice - * [start:end] of this bytearray. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. With + * optional start and end (which may be null or + * Py.None), define the effective bytearray to be the slice + * [start:end] of this bytearray. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match - * @param end of slice in this bytearray to match + * @param start of slice in this bytearray to match + * @param end of slice in this bytearray to match * @return true if and only if this[start:end] ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix, PyObject start, PyObject end) { @@ -1180,7 +1173,7 @@ /** * Append the elements in the argument sequence to the end of the array, equivalent to: - * s[len(s):len(s)] = o. The argument must be a subclass of BaseBytes or an + * s[len(s):len(s)] = o. The argument must be a subclass of {@link BaseBytes} or an * iterable type returning elements compatible with byte assignment. * * @param o the sequence of items to append to the list. @@ -1192,7 +1185,7 @@ @ExposedMethod(doc = BuiltinDocs.bytearray_extend_doc) final synchronized void bytearray_extend(PyObject o) { // Use the general method, assigning to the crack at the end of the array. - // Note this deals with all legitimate PyObject types and the case o==this. + // Note this deals with all legitimate PyObject types including the case o==this. setslice(size, size, 1, o); } @@ -1253,7 +1246,7 @@ *
    * * @param hex specification of the bytes - * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered + * @throws PyException (ValueError) if non-hex characters, or isolated ones, are encountered */ static PyByteArray fromhex(String hex) throws PyException { return bytearray_fromhex(TYPE, hex); @@ -1322,9 +1315,11 @@ /** * This type is not hashable. + * + * @throws PyException (TypeError) as this type is not hashable. */ @Override - public int hashCode() { + public int hashCode() throws PyException { return bytearray___hash__(); } @@ -1443,10 +1438,10 @@ return (PyByteArray)basebytes_upper(); } - /** - * Implementation of Python join(iterable). Return a bytearray which is the - * concatenation of the byte arrays in the iterable iterable. The separator between - * elements is the byte array providing this method. + /** + * Implementation of Python join(iterable). Return a bytearray which + * is the concatenation of the byte arrays in the iterable iterable. The separator + * between elements is the byte array providing this method. * * @param iterable of byte array objects, or objects viewable as such. * @return byte array produced by concatenation. @@ -1484,7 +1479,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray ljust(int width, String fillchar) { @@ -1498,8 +1493,8 @@ } /** - * Implementation of Python lstrip(). Return a copy of the byte array with the leading - * whitespace characters removed. + * Implementation of Python lstrip(). Return a copy of the byte array with the + * leading whitespace characters removed. * * @return a byte array containing this value stripped of those bytes */ @@ -1510,10 +1505,10 @@ /** * Implementation of Python lstrip(bytes) * - * Return a copy of the byte array with the leading characters removed. The bytes - * argument is an object specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a prefix; - * rather, all combinations of its values are stripped. + * Return a copy of the byte array with the leading characters removed. The bytes argument is an + * object specifying the set of characters to be removed. If null or + * None, the bytes argument defaults to removing whitespace. The bytes argument is + * not a prefix; rather, all combinations of its values are stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (at the left) @@ -1542,7 +1537,8 @@ } /** - * Removes and return the last element in the byte array. + * Remove and return the last element in the byte array. + * * @return PyInteger representing the value */ public PyInteger pop() { @@ -1584,7 +1580,7 @@ * argument must be a PyInteger, PyLong or string of length 1. * * @param o the value to remove from the list. - * @throws PyException ValueError if o not found in bytearray + * @throws PyException ValueError if o not found in bytearray */ public void remove(PyObject o) throws PyException { bytearray_remove(o); @@ -1746,7 +1742,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray rjust(int width, String fillchar) { @@ -1793,7 +1789,8 @@ } /** - * Implementation of Python rstrip(). Return a copy of the byte array with the trailing whitespace characters removed. + * Implementation of Python rstrip(). Return a copy of the byte array with the + * trailing whitespace characters removed. * * @return a byte array containing this value stripped of those bytes (at right) */ @@ -1804,10 +1801,10 @@ /** * Implementation of Python rstrip(bytes) * - * Return a copy of the byte array with the trailing characters removed. The bytes - * argument is an object specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a suffix; - * rather, all combinations of its values are stripped. + * Return a copy of the byte array with the trailing characters removed. The bytes argument is + * an object specifying the set of characters to be removed. If null or + * None, the bytes argument defaults to removing whitespace. The bytes argument is + * not a suffix; rather, all combinations of its values are stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (at right) @@ -1844,11 +1841,12 @@ * Implementation of Python startswith(prefix). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @return true if and only if this bytearray starts with the prefix (or one of them) + * @return true if and only if this bytearray starts with the prefix (or one of + * them) */ public boolean startswith(PyObject prefix) { return basebytes_starts_or_endswith(prefix, null, null, false); @@ -1858,13 +1856,14 @@ * Implementation of Python startswith( prefix [, start ] ). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. With optional - * start (which may be null or Py.None), define the - * effective bytearray to be the slice [start:] of this bytearray. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. With + * optional start (which may be null or Py.None), define + * the effective bytearray to be the slice [start:] of this + * bytearray. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match + * @param start of slice in this bytearray to match * @return true if and only if this[start:] starts with the prefix (or one of them) */ public boolean startswith(PyObject prefix, PyObject start) { @@ -1875,15 +1874,15 @@ * Implementation of Python startswith( prefix [, start [, end ]] ). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. With optional - * start and end (which may be null or - * Py.None), define the effective bytearray to be the slice - * [start:end] of this bytearray. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. With + * optional start and end (which may be null or + * Py.None), define the effective bytearray to be the slice + * [start:end] of this bytearray. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match - * @param end of slice in this bytearray to match + * @param start of slice in this bytearray to match + * @param end of slice in this bytearray to match * @return true if and only if this[start:end] starts with the prefix (or one of them) */ public boolean startswith(PyObject prefix, PyObject start, PyObject end) { @@ -1896,8 +1895,8 @@ } /** - * Implementation of Python strip(). Return a copy of the byte array with the leading - * and trailing whitespace characters removed. + * Implementation of Python strip(). Return a copy of the byte array with the + * leading and trailing whitespace characters removed. * * @return a byte array containing this value stripped of those bytes (left and right) */ @@ -1909,9 +1908,10 @@ * Implementation of Python strip(bytes) * * Return a copy of the byte array with the leading and trailing characters removed. The bytes - * argument is anbyte arrayt specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a prefix or suffix; - * rather, all combinations of its values are stripped. + * argument is anbyte arrayt specifying the set of characters to be removed. If + * null or None, the bytes argument defaults to removing whitespace. + * The bytes argument is not a prefix or suffix; rather, all combinations of its values are + * stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (left and right) @@ -1980,13 +1980,13 @@ * Implementation of Python translate(table). * * Return a copy of the byte array where all bytes occurring in the optional argument - * deletechars are removed, and the remaining bytes have been mapped through the given - * translation table, which must be of length 256. + * deletechars are removed, and the remaining bytes have been mapped through the + * given translation table, which must be of length 256. * * @param table length 256 translation table (of a type that may be regarded as a byte array) * @return translated byte array */ - public PyByteArray translate(PyObject table) { + public PyByteArray translate(PyObject table) { return bytearray_translate(table, null); } @@ -1994,12 +1994,12 @@ * Implementation of Python translate(table[, deletechars]). * * Return a copy of the byte array where all bytes occurring in the optional argument - * deletechars are removed, and the remaining bytes have been mapped through the given - * translation table, which must be of length 256. + * deletechars are removed, and the remaining bytes have been mapped through the + * given translation table, which must be of length 256. * - * You can use the maketrans() helper function in the string module to create a translation - * table. For string objects, set the table argument to None for translations that only delete - * characters: + * You can use the Python maketrans() helper function in the string + * module to create a translation table. For string objects, set the table argument to + * None for translations that only delete characters: * * @param table length 256 translation table (of a type that may be regarded as a byte array) * @param deletechars object that may be regarded as a byte array, defining bytes to delete @@ -2131,6 +2131,7 @@ * * @param needed becomes the new value of this.size */ + @Override protected void newStorage(int needed) { if (needed > 0) { final int L = recLength(needed); @@ -2168,7 +2169,7 @@ * been preserved, although probably moved, and the gap between them has been adjusted to the * requested size. *

    - * The effect on this PyByteArray is that: + * The effect on this PyByteArray is that: * *

          * this.offset = f'
    @@ -2277,7 +2278,8 @@
          * where the regions of length a and b=size-(a+d) have been preserved
          * and the gap between them adjusted to specification. The new offset f' is chosen heuristically
          * by the method to optimise the efficiency of repeated adjustment near either end of the array,
    -     * e.g. repeated prepend or append operations. The effect on this PyByteArray is that:
    +     * e.g. repeated prepend or append operations. The effect on this PyByteArray is
    +     * that:
          *
          * 
          * this.offset = f'
    @@ -2357,7 +2359,8 @@
          * where the regions of length a and b=size-(a+d) have been preserved
          * and the gap between them adjusted to specification. The new offset f' is chosen heuristically
          * by the method to optimise the efficiency of repeated adjustment near either end of the array,
    -     * e.g. repeated prepend or append operations. The effect on this PyByteArray is that:
    +     * e.g. repeated prepend or append operations. The effect on this PyByteArray is
    +     * that:
          *
          * 
          * this.offset = f'
    @@ -2439,7 +2442,7 @@
          *
          * where the contents of region a have been preserved, although possbly moved, and
          * the gap at the end has the requested size. this method never shrinks the total storage. The
    -     * effect on this PyByteArray is that:
    +     * effect on this PyByteArray is that:
          *
          * 
          * this.offset = f or 0
    @@ -2518,7 +2521,7 @@
          * 
    * * where the regions of length a and b=size-(a+d) have been preserved - * and the gap between them eliminated. The effect on this PyByteArray is that: + * and the gap between them eliminated. The effect on this PyByteArray is that: * *
          * this.offset = f'
    @@ -2526,8 +2529,8 @@
          * 
    * * The method does not implement the Python repertoire of slice indices but avoids indexing - * outside the bytearray by silently adjusting a to be within it. Negative d is treated as 0 and - * if d is too large, it is truncated to the array end. + * outside the bytearray by silently adjusting a to be within it. Negative d is + * treated as 0 and if d is too large, it is truncated to the array end. * * @param a index of hole in byte array * @param d number to discard (will discard x[a,a+d-1]) @@ -2536,8 +2539,7 @@ private void storageDelete(int a, int d) { // storageReplace specialised for delete (e=0) - if (d == 0) - { + if (d == 0) { return; // Everything stays where it is. } @@ -2626,7 +2628,7 @@ * where the regions of length a and b=size-(a+e) have been preserved * and the e intervening elements reduced to e-d elements, by removing * exactly the elements with indices (relative to the start of valid data) a+k*c - * for k=0...d-1. The effect on this PyByteArray is that: + * for k=0...d-1. The effect on this PyByteArray is that: * *
          * this.offset = f'
    @@ -2634,8 +2636,8 @@
          * 
    * * The method does not implement the Python repertoire of slice indices but avoids indexing - * outside the bytearray by silently adjusting a to be within it. Negative d is treated as 0 and - * if d is too large, it is truncated to the array end. + * outside the bytearray by silently adjusting a to be within it. Negative d is + * treated as 0 and if d is too large, it is truncated to the array end. * * @param a index of hole in byte array * @param c (>0) step size between the locations of elements to delete @@ -2647,4 +2649,3 @@ // XXX Change SequenceIndexDelegate to use (and PyList to implement) delslice() } } - -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 12 16:29:18 2013 From: jython-checkins at python.org (jeff.allen) Date: Tue, 12 Feb 2013 16:29:18 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Add_skips_to_test=2Etest=5F?= =?utf-8?q?io?= Message-ID: <3Z577y34RhzQLn@mail.python.org> http://hg.python.org/jython/rev/a0bbf3bff23b changeset: 7046:a0bbf3bff23b user: Jeff Allen date: Mon Feb 11 23:56:01 2013 +0000 summary: Add skips to test.test_io This converts the outstanding 6 errors to skips, now scoring 0/0/71. There were really only two problems: issue 1996 (multiple inheritance with slots) and the issue that _pyio.open (from CPython) checks the file descriptor is an int. I've decided that's not a bug in Jython. files: Lib/test/test_io.py | 38 +++++++++++++++++++++++++++++++++ 1 files changed, 38 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -634,6 +634,23 @@ "len(array.array) returns number of elements rather than bytelength" )(IOTest.test_array_writes) + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_invalid_operations(self): + pass + + # Jython does not use integer file descriptors but an object instead. + # Unfortunately, _pyio.open checks that it is an int. + # Override the affected test versions just so we can skip them visibly. + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_closefd_attr(self): + pass + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_read_closed(self): + pass + class CommonBufferedTests: # Tests common to BufferedReader, BufferedWriter and BufferedRandom @@ -1371,6 +1388,13 @@ class PyBufferedRWPairTest(BufferedRWPairTest): tp = pyio.BufferedRWPair + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest): read_mode = "rb+" @@ -2719,6 +2743,20 @@ class PyMiscIOTest(MiscIOTest): io = pyio + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_io_after_close(self): + pass + + # Jython does not use integer file descriptors but an object instead. + # Unfortunately, _pyio.open checks that it is an int. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_attributes(self): + pass + @unittest.skipIf(os.name == 'nt' or (sys.platform[:4] == 'java' and os._name == 'nt'), -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 12 16:29:19 2013 From: jython-checkins at python.org (jeff.allen) Date: Tue, 12 Feb 2013 16:29:19 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Javadoc_changes_only=2E?= Message-ID: <3Z577z6hMNzMYT@mail.python.org> http://hg.python.org/jython/rev/0f9f62df2182 changeset: 7047:0f9f62df2182 user: Jeff Allen date: Tue Feb 12 00:45:24 2013 +0000 summary: Javadoc changes only. Loosely-related Javadoc errors corrected. No code changes. files: src/org/python/core/buffer/SimpleStringBuffer.java | 10 +++--- src/org/python/core/codecs.java | 10 +++--- src/org/python/modules/_codecs.java | 8 ++--- src/org/python/modules/_io/OpenMode.java | 9 ++--- src/org/python/modules/_io/PyIOBase.java | 1 - src/org/python/modules/_io/PyRawIOBase.java | 2 +- src/org/python/util/ProxyCompiler.java | 15 +++++---- 7 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/org/python/core/buffer/SimpleStringBuffer.java b/src/org/python/core/buffer/SimpleStringBuffer.java --- a/src/org/python/core/buffer/SimpleStringBuffer.java +++ b/src/org/python/core/buffer/SimpleStringBuffer.java @@ -6,11 +6,11 @@ /** * Buffer API that appears to be a one-dimensional array of one-byte items providing read-only API, * but which is actually backed by a Java String. Some of the buffer API absolutely needs access to - * the data as a byte array (those parts that involve a {@link PyBuffer.Pointer} result), and therefore - * this class must create a byte array from the String for them. However, it defers creation of a - * byte array until that part of the API is actually used. Where possible, this class overrides - * those methods in SimpleBuffer that would otherwise access the byte array attribute to use the - * String instead. + * the data as a byte array (those parts that involve a PyBuffer.Pointer result), and + * therefore this class must create a byte array from the String for them. However, it defers + * creation of a byte array until that part of the API is actually used. Where possible, this class + * overrides those methods in SimpleBuffer that would otherwise access the byte array attribute to + * use the String instead. */ public class SimpleStringBuffer extends SimpleBuffer { diff --git a/src/org/python/core/codecs.java b/src/org/python/core/codecs.java --- a/src/org/python/core/codecs.java +++ b/src/org/python/core/codecs.java @@ -919,11 +919,11 @@ * This method differs from the CPython equivalent (in Object/unicodeobject.c) * which works with an array of code points that are, in a wide build, Unicode code points. * - * @param unicode - * @param base64SetO - * @param base64WhiteSpace - * @param errors - * @return + * @param unicode to be encoded + * @param base64SetO true if characters in "set O" should be translated to base64 + * @param base64WhiteSpace true if white-space characters should be translated to base64 + * @param errors error policy name (e.g. "ignore", "replace") + * @return bytes representing the encoded unicode string */ public static String PyUnicode_EncodeUTF7(String unicode, boolean base64SetO, boolean base64WhiteSpace, String errors) { diff --git a/src/org/python/modules/_codecs.java b/src/org/python/modules/_codecs.java --- a/src/org/python/modules/_codecs.java +++ b/src/org/python/modules/_codecs.java @@ -314,12 +314,12 @@ * @param mapping to convert bytes to characters * @return decoded string and number of bytes consumed */ - public static PyTuple charmap_decode(String str, String errors, PyObject mapping) { + public static PyTuple charmap_decode(String bytes, String errors, PyObject mapping) { if (mapping == null || mapping == Py.None) { // Default to Latin-1 - return latin_1_decode(str, errors); + return latin_1_decode(bytes, errors); } else { - return charmap_decode(str, errors, mapping, false); + return charmap_decode(bytes, errors, mapping, false); } } @@ -1290,7 +1290,6 @@ * @param bytes to be decoded (Jython {@link PyString} convention) * @param errors error policy name (e.g. "ignore", "replace") * @param byteorder decoding "endianness" specified (in the Python -1, 0, +1 convention) - * @param isFinal if a "final" call, meaning the input must all be consumed * @return tuple (unicode_result, bytes_consumed, endianness) */ public static PyTuple utf_32_ex_decode(String bytes, String errors, int byteorder) { @@ -1335,7 +1334,6 @@ * @param order LE, BE or UNDEFINED (meaning bytes may begin with a byte order mark) * @param isFinal if a "final" call, meaning the input must all be consumed * @param findOrder if the returned tuple should include a report of the byte order - * @return tuple containing unicode result (as UTF-16 Java String) * @return tuple (unicode_result, bytes_consumed [, endianness]) */ private static PyTuple PyUnicode_DecodeUTF32Stateful(String bytes, String errors, diff --git a/src/org/python/modules/_io/OpenMode.java b/src/org/python/modules/_io/OpenMode.java --- a/src/org/python/modules/_io/OpenMode.java +++ b/src/org/python/modules/_io/OpenMode.java @@ -43,8 +43,7 @@ /** * Error message describing the way in which the mode is invalid, or null if no problem has been * found. This field may be set by the constructor (in the case of duplicate or unrecognised - * mode letters), by the {@link #isValid()} method, or by client code. A non-null value will - * cause {@link #isValid()} to return false. + * mode letters), by the {@link #validate()} method, or by client code. */ public String message; @@ -52,7 +51,7 @@ * Decode the given string to an OpenMode object, checking for duplicate or unrecognised mode * letters. Valid letters are those in "rwa+btU". Errors in the mode string do not raise an * exception, they simply generate an appropriate error message in {@link #message}. After - * construction, a client should always call {@link #isValid()} to complete validity checks. + * construction, a client should always call {@link #validate()} to complete validity checks. * * @param mode */ @@ -196,7 +195,7 @@ */ public void checkValid() throws PyException { - // Actually peform the check + // Actually perform the check validate(); // The 'other' flag reports alien symbols in the original mode string @@ -215,7 +214,7 @@ /** * The mode string we need when constructing a FileIO initialised with the present * mode. Note that this is not the same as the full open mode because it omits the text-based - * attributes, and not the same as {@link #raw()}. + * attributes. * * @return "r", "w", or "a" with optional "+". */ diff --git a/src/org/python/modules/_io/PyIOBase.java b/src/org/python/modules/_io/PyIOBase.java --- a/src/org/python/modules/_io/PyIOBase.java +++ b/src/org/python/modules/_io/PyIOBase.java @@ -160,7 +160,6 @@ * Truncate file to size bytes to the current position (as reported by * tell()). * - * @param size requested for stream (or null for default) * @return the new size */ public long truncate() { diff --git a/src/org/python/modules/_io/PyRawIOBase.java b/src/org/python/modules/_io/PyRawIOBase.java --- a/src/org/python/modules/_io/PyRawIOBase.java +++ b/src/org/python/modules/_io/PyRawIOBase.java @@ -187,7 +187,7 @@ * of bytes written. * * @param b buffer of bytes to be written - * @return + * @return the number of bytes written */ public PyObject write(PyObject b) { return _RawIOBase_write(b); diff --git a/src/org/python/util/ProxyCompiler.java b/src/org/python/util/ProxyCompiler.java --- a/src/org/python/util/ProxyCompiler.java +++ b/src/org/python/util/ProxyCompiler.java @@ -5,21 +5,22 @@ import org.python.core.PySystemState; public class ProxyCompiler { + /** * Compiles the python file by loading it - * - * FIXME: this is quite hackish right now. It basically starts a whole interpreter - * and set's proxyDebugDirectory as the destination for the compiled proxy class - * - * @param filename: python filename to exec - * @param destDir: destination directory for the proxy classes + * + * FIXME: this is quite hackish right now. It basically starts a whole interpreter and sets + * proxyDebugDirectory as the destination for the compiled proxy class + * + * @param filename python filename to exec + * @param destDir destination directory for the proxy classes */ public static void compile(String filename, String destDir) { Properties props = new Properties(System.getProperties()); props.setProperty(PySystemState.PYTHON_CACHEDIR_SKIP, "true"); PySystemState.initialize(props, null); PythonInterpreter interp = new PythonInterpreter(); - + String origProxyDir = org.python.core.Options.proxyDebugDirectory; try { org.python.core.Options.proxyDebugDirectory = destDir; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 12 16:29:21 2013 From: jython-checkins at python.org (jeff.allen) Date: Tue, 12 Feb 2013 16:29:21 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Javadoc_and_comment_tweaks_?= =?utf-8?q?=28mostly_Buffer_API=29?= Message-ID: <3Z57814LGzzN9s@mail.python.org> http://hg.python.org/jython/rev/1da2e1095c31 changeset: 7048:1da2e1095c31 user: Jeff Allen date: Tue Feb 12 11:09:49 2013 +0000 summary: Javadoc and comment tweaks (mostly Buffer API) No code change. files: src/org/python/core/PyBUF.java | 52 +++++----- src/org/python/core/PyBuffer.java | 43 +++---- src/org/python/core/buffer/BaseBuffer.java | 9 +- src/org/python/core/buffer/SimpleBuffer.java | 2 + src/org/python/core/buffer/Strided1DBuffer.java | 25 +++- src/org/python/core/buffer/Strided1DWritableBuffer.java | 2 +- src/org/python/modules/SHA224Digest.java | 12 +- 7 files changed, 77 insertions(+), 68 deletions(-) diff --git a/src/org/python/core/PyBUF.java b/src/org/python/core/PyBUF.java --- a/src/org/python/core/PyBUF.java +++ b/src/org/python/core/PyBUF.java @@ -3,20 +3,20 @@ /** * This interface provides a base for the key interface of the buffer API, {@link PyBuffer}, * including symbolic constants used by the consumer of a PyBuffer to specify its - * requirements and assumptions. The Jython buffer API emulates the CPython buffer API. + * requirements and assumptions. The Jython buffer API emulates the CPython buffer API. There are + * two reasons for separating parts of PyBuffer into this interface: *
      - *
    • There are two reasons for separating parts of PyBuffer into this interface: The - * constants defined in CPython have the names PyBUF_SIMPLE, + *
    • The constants defined in CPython have the names PyBUF_SIMPLE, * PyBUF_WRITABLE, etc., and the trick of defining ours here means we can write - * {@link PyBUF#SIMPLE}, {@link PyBUF#WRITABLE}, etc. so source code looks similar.
    • + * PyBUF.SIMPLE, PyBUF.WRITABLE, etc. so source code looks similar. *
    • It is not so easy in Java as it is in C to treat a byte array as storing * anything other than byte, and we prepare for the possibility of buffers with a * series of different primitive types by defining here those methods that would be in common - * between (Byte)Buffer and an assumed future FloatBuffer or + * between (Byte)Buffer and an assumed future FloatBuffer or * TypedBuffer<T>. (Compare java.nio.Buffer.)
    • *
    - * Except for other interfaces, it is unlikely any classes would implement PyBUF - * directly. Users of the Jython buffer API can mostly overlook the distinction and just use + * It is unlikely any classes would implement PyBUF, except indirectly through other + * interfaces. Users of the Jython buffer API can mostly overlook the distinction and just use * PyBuffer. */ public interface PyBUF { @@ -39,12 +39,12 @@ /** * An array reporting the size of the buffer, considered as a multidimensional array, in each - * dimension and (by its length) number of dimensions. The size is the size in "items". An item - * is the amount of buffer content addressed by one index or set of indices. In the simplest - * case an item is a single unit (byte), and there is one dimension. In complex cases, the array - * is multi-dimensional, and the item at each location is multi-unit (multi-byte). The consumer - * must not modify this array. A valid shape array is always returned (difference - * from CPython). + * dimension and (by its length) giving the number of dimensions. The size of the buffer is its + * size in "items". An item is the amount of buffer content addressed by one index or set of + * indices. In the simplest case an item is a single unit (byte), and there is one dimension. In + * complex cases, the array is multi-dimensional, and the item at each location is multi-unit + * (multi-byte). The consumer must not modify this array. A valid shape array is + * always returned (difference from CPython). * * @return the dimensions of the buffer as an array */ @@ -59,7 +59,7 @@ /** * The total number of units (bytes) stored, which will be the product of the elements of the - * shape array, and the item size. + * shape array, and the item size in units. * * @return the total number of units stored. */ @@ -81,13 +81,13 @@ /** * The suboffsets array is a further part of the support for interpreting the * buffer as an n-dimensional array of items, where the array potentially uses indirect - * addressing (like a real Java array of arrays, in fact). This is only applicable when there - * are more than 1 dimension and works in conjunction with the strides array. (More + * addressing (like a real Java array of arrays, in fact). This is only applicable when there is + * more than 1 dimension, and it works in conjunction with the strides array. (More * on this in the CPython documentation.) When used, suboffsets[k] is an integer - * index, bit a byte offset as in CPython. The consumer must not modify this array. When not + * index, not a byte offset as in CPython. The consumer must not modify this array. When not * needed for navigation null is returned (as in CPython). * - * @return suboffsets array or null in not necessary for navigation + * @return suboffsets array or null if not necessary for navigation */ int[] getSuboffsets(); @@ -108,15 +108,15 @@ static final int MAX_NDIM = 64; /** * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to - * specify that it expects to write to the buffer contents. getBuffer will raise an exception if - * the exporter's buffer cannot meet this requirement. + * specify that it expects to write to the buffer contents. getBuffer will raise an + * exception if the exporter's buffer cannot meet this requirement. */ static final int WRITABLE = 0x0001; /** * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to * specify that it assumes a simple one-dimensional organisation of the exported storage with - * item size of one. getBuffer will raise an exception if the consumer sets this flag and the - * exporter's buffer cannot be navigated that simply. + * item size of one. getBuffer will raise an exception if the consumer sets this + * flag and the exporter's buffer cannot be navigated that simply. */ static final int SIMPLE = 0; /** @@ -162,7 +162,7 @@ * specify that it will assume a contiguous organisation of the units, but will enquire which * organisation it actually is. * - * getBuffer will raise an exception if the exporter's buffer is not contiguous. + * getBuffer will raise an exception if the exporter's buffer is not contiguous. * ANY_CONTIGUOUS implies STRIDES. */ // Further CPython strangeness since it uses the strides array to answer the enquiry. @@ -216,7 +216,7 @@ /* Constants for readability, not standard for CPython */ /** - * Field mask, use as in if ((flags&NAVIGATION) == STRIDES) .... The importance of + * Field mask, used as in if ((flags&NAVIGATION) == STRIDES) .... The importance of * the subset of flags defined by this mask is not so much in their "navigational" character as * in the way they are treated in a buffer request. *

    @@ -243,7 +243,7 @@ */ static final int IS_F_CONTIGUOUS = F_CONTIGUOUS & ~STRIDES; /** - * Field mask, use as in if ((flags&CONTIGUITY)== ... ) .... + * Field mask, used as in if ((flags&CONTIGUITY)== ... ) .... */ static final int CONTIGUITY = (C_CONTIGUOUS | F_CONTIGUOUS | ANY_CONTIGUOUS) & ~STRIDES; -} \ No newline at end of file +} diff --git a/src/org/python/core/PyBuffer.java b/src/org/python/core/PyBuffer.java --- a/src/org/python/core/PyBuffer.java +++ b/src/org/python/core/PyBuffer.java @@ -163,9 +163,9 @@ * When a PyBuffer is the target, the same checks are carried out on the consumer * flags, and a return will normally be a reference to that buffer. A Jython * PyBuffer keeps count of these re-exports in order to match them with the number - * of calls to {@link #release()}. When the last matching release() arrives it is considered - * "final", and release actions may then take place on the exporting object. After the final - * release of a buffer, a call to getBuffer should raise an exception. + * of calls to {@link #release()}. When the last matching release() arrives it is + * considered "final", and release actions may then take place on the exporting object. After + * the final release of a buffer, a call to getBuffer should raise an exception. */ @Override PyBuffer getBuffer(int flags) throws PyException; @@ -213,16 +213,17 @@ * this.getPointer(i). A request for a slice where start = s, * length = N and stride = m, results in a buffer * y such that y(k) = x(s+km) where k=0..(N-1). In Python terms, this is - * the slice x[s : s+(N-1)m+1 : m] (if m>0) or the slice x[s : s+(N-1)m-1 : m] - * (if m<0). Implementations should check that this range is entirely within the current - * buffer. + * the slice x[s : s+(N-1)m+1 : m] (if m>0) or the slice x[s : s+(N-1)m-1 : + * m] (if m<0). Implementations should check that this range is entirely within + * the current buffer. *

    * In a simple buffer backed by a contiguous byte array, the result is a strided PyBuffer on the * same storage but where the offset is adjusted by s and the stride is as supplied. If * the current buffer is already strided and/or has an item size larger than single bytes, the - * new start index, length and stride will be translated from the arguments given, through this - * buffer's stride and item size. The consumer always expresses start and - * strides in terms of the abstract view of this buffer. + * new start index, length and stride will be translated + * from the arguments given, through this buffer's stride and item size. The caller always + * expresses start and strides in terms of the abstract view of this + * buffer. * * @param flags specifying features demanded and the navigational capabilities of the consumer * @param start index in the current buffer @@ -265,13 +266,11 @@ * Return a structure describing the slice of a byte array that holds the data being exported to * the consumer. For a one-dimensional contiguous buffer, assuming the following client code * where obj has type BufferProtocol: - * *

          * PyBuffer a = obj.getBuffer();
          * int itemsize = a.getItemsize();
          * PyBuffer.Pointer b = a.getBuf();
          * 
    - * * the item with index k is in the array b.storage at index * [b.offset + k*itemsize] to [b.offset + (k+1)*itemsize - 1] * inclusive. And if itemsize==1, the item is simply the byte @@ -287,17 +286,15 @@ PyBuffer.Pointer getBuf(); /** - * Return a structure describing the slice of a byte array that points to a single item from the - * data being exported to the consumer. For a one-dimensional contiguous buffer, assuming the + * Return a structure describing the position in a byte array of a single item from the data + * being exported to the consumer. For a one-dimensional contiguous buffer, assuming the * following client code where obj has type BufferProtocol: - * *
          * int k = ... ;
          * PyBuffer a = obj.getBuffer();
          * int itemsize = a.getItemsize();
          * PyBuffer.Pointer b = a.getPointer(k);
          * 
    - * * the item with index k is in the array b.storage at index * [b.offset] to [b.offset + itemsize - 1] inclusive. And if * itemsize==1, the item is simply the byte b.storage[b.offset] @@ -312,11 +309,10 @@ PyBuffer.Pointer getPointer(int index); /** - * Return a structure describing the slice of a byte array that points to a single item from the - * data being exported to the consumer, in the case that array may be multi-dimensional. For a + * Return a structure describing the position in a byte array of a single item from the data + * being exported to the consumer, in the case that array may be multi-dimensional. For a * 3-dimensional contiguous buffer, assuming the following client code where obj * has type BufferProtocol: - * *
          * int i, j, k;
          * // ... calculation that assigns i, j, k
    @@ -324,18 +320,17 @@
          * int itemsize = a.getItemsize();
          * PyBuffer.Pointer b = a.getPointer(i,j,k);
          * 
    - * * the item with index [i,j,k] is in the array b.storage at index * [b.offset] to [b.offset + itemsize - 1] inclusive. And if * itemsize==1, the item is simply the byte b.storage[b.offset] *

    * Essentially this is a method for computing the offset of a particular index. The client is * free to navigate the underlying buffer b.storage without respecting these - * boundaries. - *

    - * If the buffer is also non-contiguous, b.storage[b.offset] is still the (first - * byte of) the item at index [0,...,0]. However, it is necessary to navigate b - * using the shape, strides and sub-offsets provided by the API. + * boundaries. If the buffer is non-contiguous, the above description is still valid (since a + * multi-byte item must itself be contiguously stored), but in any additional navigation of + * b.storage[] to other units, the client must use the shape, strides and + * sub-offsets provided by the API. Normally one starts b = a.getBuf() in order to + * establish the offset of index [0,...,0]. * * @param indices multidimensional index at which to position the pointer * @return structure defining the byte[] slice that is the shared data diff --git a/src/org/python/core/buffer/BaseBuffer.java b/src/org/python/core/buffer/BaseBuffer.java --- a/src/org/python/core/buffer/BaseBuffer.java +++ b/src/org/python/core/buffer/BaseBuffer.java @@ -22,7 +22,7 @@ * passed to the constructor. Otherwise, all methods for write access raise a * BufferError read-only exception and {@link #isReadonly()} returns true. * Sub-classes can follow the same pattern, setting {@link PyBUF#WRITABLE} in the constructor and, - * if they have to override the operations that write (storeAt and + * if they have to, overriding the operations that write (storeAt and * copyFrom). The recommended pattern is: * *

    @@ -31,9 +31,10 @@
      * }
      * // ... implementation of the write operation
      * 
    - * - * The implementors of simple buffers will find it efficient to override the generic access methods - * to which performance might be sensitive, with a calculation specific to their actual type. + * Another approach, used in the standard library, is to have distinct classes for the writable and + * read-only variants. The implementors of simple buffers will find it efficient to override the + * generic access methods to which performance might be sensitive, with a calculation specific to + * their actual type. *

    * At the time of writing, only one-dimensional buffers of item size one are used in the Jython * core. diff --git a/src/org/python/core/buffer/SimpleBuffer.java b/src/org/python/core/buffer/SimpleBuffer.java --- a/src/org/python/core/buffer/SimpleBuffer.java +++ b/src/org/python/core/buffer/SimpleBuffer.java @@ -48,6 +48,7 @@ * @throws ArrayIndexOutOfBoundsException if index0 and size are * inconsistent with storage.length */ + // XXX: "for sub-class use" = should be protected? public SimpleBuffer(byte[] storage, int index0, int size) throws PyException, ArrayIndexOutOfBoundsException { this(); @@ -90,6 +91,7 @@ * @param storage the array of bytes storing the implementation of the exporting object * @throws NullPointerException if storage is null */ + // XXX: "for sub-class use" = should be protected? public SimpleBuffer(byte[] storage) throws NullPointerException { this(); this.storage = storage; // Exported data (index0=0 from initialisation) diff --git a/src/org/python/core/buffer/Strided1DBuffer.java b/src/org/python/core/buffer/Strided1DBuffer.java --- a/src/org/python/core/buffer/Strided1DBuffer.java +++ b/src/org/python/core/buffer/Strided1DBuffer.java @@ -9,21 +9,24 @@ * properties in the usual way, designating a slice (or all) of a byte array, but also a * stride property (equal to getStrides()[0]). *

    - * Let this underlying buffer be the byte array u(i) for i=0..N-1, let x be the + * Let the underlying buffer be the byte array u(i) for i=0..N-1, let x be the * Strided1DBuffer, and let the stride be p. The storage works as follows. * Designate by x(j), for j=0..L-1, the byte at index j, that is, the byte - * retrieved by x.byteAt(j). Then, we store x(j) at u(a+pj), that is, - * x(0) is at u(a). When we construct such a buffer, we have to supply a = + * retrieved by x.byteAt(j). Thus, we store x(j) at u(a+pj), that is, + * x(0) = u(a). When we construct such a buffer, we have to supply a = * index0, L = length, and p = stride as the * constructor arguments. The last item in the slice x(L-1) is stored at u(a+p(L-1)). - * If p<0 and L>1, this will be to the left of u(a), so the constructor - * argument index0 is not then the low index of the range occupied by the data. Clearly both these - * indexes must be in the range 0 to N-1 inclusive, a rule enforced by the constructors + * For the simple case of positive stride, constructor argument index0 is the low index + * of the range occupied by the data. When the stride is negative, that is to say p<0, and + * L>1, this will be to the left of u(a), and the constructor argument + * index0 is not then the low index of the range occupied by the data. Clearly both + * these indexes must be in the range 0 to N-1 inclusive, a rule enforced by the constructors * (unless L=0, when it is assumed no array access will take place). *

    * The class may be used by exporters to create a strided slice (e.g. to export the diagonal of a * matrix) and in particular by other buffers to create strided slices of themselves, such as to - * create the memoryview that is returned as an extended slice of a memoryview. + * create the memoryview that is returned as an extended slice of a + * memoryview. */ public class Strided1DBuffer extends BaseBuffer { @@ -60,9 +63,14 @@ /** * Provide an instance of Strided1DBuffer with navigation variables initialised, - * for sub-class use. The buffer ( {@link #storage}, {@link #index0}), and the navigation ( + * for sub-class use. The buffer ({@link #storage}, {@link #index0}), and the navigation ( * {@link #shape} array and {@link #stride}) will be initialised from the arguments (which are * checked for range). + *

    + * The sub-class constructor should check that the intended access is compatible with this + * object by calling {@link #checkRequestFlags(int)}. (See the source of + * {@link Strided1DWritableBuffer#Strided1DWritableBuffer(int, byte[], int, int, int)} + * for an example of this use.) * * @param storage raw byte array containing exported data * @param index0 index into storage of item[0] @@ -72,6 +80,7 @@ * @throws ArrayIndexOutOfBoundsException if index0, length and * stride are inconsistent with storage.length */ + // XXX: "for sub-class use" = should be protected? public Strided1DBuffer(byte[] storage, int index0, int length, int stride) throws ArrayIndexOutOfBoundsException, NullPointerException { this(); diff --git a/src/org/python/core/buffer/Strided1DWritableBuffer.java b/src/org/python/core/buffer/Strided1DWritableBuffer.java --- a/src/org/python/core/buffer/Strided1DWritableBuffer.java +++ b/src/org/python/core/buffer/Strided1DWritableBuffer.java @@ -88,7 +88,7 @@ if (length > 0) { // Translate start relative to underlying buffer - int compStride= this.stride * stride; + int compStride = this.stride * stride; int compIndex0 = index0 + start * this.stride; // Construct a view, taking a lock on the root object (this or this.root) return new SlicedView(getRoot(), flags, storage, compIndex0, length, compStride); diff --git a/src/org/python/modules/SHA224Digest.java b/src/org/python/modules/SHA224Digest.java --- a/src/org/python/modules/SHA224Digest.java +++ b/src/org/python/modules/SHA224Digest.java @@ -1,6 +1,6 @@ package org.python.modules; -/** +/* * Copyright 2011 Gaurav Raje * Licensed to PSF under a Contributor Agreement. */ @@ -8,13 +8,14 @@ /** * SHA-224 as described in RFC 3874. This introduces the SHA224 Digest which has - * been ommitted from the JDK {@link java.security}. - * + * been omitted from the JDK java.security. * * This implementation has been borrowed from the Bouncy Castle implementation - * of SHA2 algorithms. Since they are MIT Licensed, they are compatible with + * of SHA2 algorithms. + * + * Since they are MIT Licensed, they are compatible with * this project. Their mandatory copyright notice follows. - * + *

      * Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle
      * (http://www.bouncycastle.org)
      * 
    @@ -35,6 +36,7 @@
      * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      * SOFTWARE.
    + * 
    */ public class SHA224Digest extends MessageDigest -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 12 16:29:24 2013 From: jython-checkins at python.org (jeff.allen) Date: Tue, 12 Feb 2013 16:29:24 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge?= Message-ID: <3Z57840Z5GzN9s@mail.python.org> http://hg.python.org/jython/rev/1af38173c96d changeset: 7049:1af38173c96d parent: 7044:ef5741dd9b6d parent: 7048:1da2e1095c31 user: Jeff Allen date: Tue Feb 12 14:26:21 2013 +0000 summary: Merge files: Lib/test/test_io.py | 38 + src/org/python/core/BaseBytes.java | 402 ++++++--- src/org/python/core/PyBUF.java | 52 +- src/org/python/core/PyBuffer.java | 43 +- src/org/python/core/PyByteArray.java | 401 +++++---- src/org/python/core/buffer/BaseBuffer.java | 9 +- src/org/python/core/buffer/SimpleBuffer.java | 2 + src/org/python/core/buffer/SimpleStringBuffer.java | 10 +- src/org/python/core/buffer/Strided1DBuffer.java | 25 +- src/org/python/core/buffer/Strided1DWritableBuffer.java | 2 +- src/org/python/core/codecs.java | 10 +- src/org/python/modules/SHA224Digest.java | 12 +- src/org/python/modules/_codecs.java | 8 +- src/org/python/modules/_io/OpenMode.java | 9 +- src/org/python/modules/_io/PyIOBase.java | 1 - src/org/python/modules/_io/PyRawIOBase.java | 2 +- src/org/python/util/ProxyCompiler.java | 15 +- 17 files changed, 600 insertions(+), 441 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -634,6 +634,23 @@ "len(array.array) returns number of elements rather than bytelength" )(IOTest.test_array_writes) + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_invalid_operations(self): + pass + + # Jython does not use integer file descriptors but an object instead. + # Unfortunately, _pyio.open checks that it is an int. + # Override the affected test versions just so we can skip them visibly. + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_closefd_attr(self): + pass + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_read_closed(self): + pass + class CommonBufferedTests: # Tests common to BufferedReader, BufferedWriter and BufferedRandom @@ -1371,6 +1388,13 @@ class PyBufferedRWPairTest(BufferedRWPairTest): tp = pyio.BufferedRWPair + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest): read_mode = "rb+" @@ -2719,6 +2743,20 @@ class PyMiscIOTest(MiscIOTest): io = pyio + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_io_after_close(self): + pass + + # Jython does not use integer file descriptors but an object instead. + # Unfortunately, _pyio.open checks that it is an int. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_attributes(self): + pass + @unittest.skipIf(os.name == 'nt' or (sys.platform[:4] == 'java' and os._name == 'nt'), diff --git a/src/org/python/core/BaseBytes.java b/src/org/python/core/BaseBytes.java --- a/src/org/python/core/BaseBytes.java +++ b/src/org/python/core/BaseBytes.java @@ -9,12 +9,12 @@ import java.util.ListIterator; /** - * Base class for Jython bytearray (and bytes in due course) that provides most of the Java API, - * including Java List behaviour. Attempts to modify the contents through this API will throw a - * TypeError if the actual type of the object is not mutable. - *

    - * It is possible for a Java client to treat this class as a List<PyInteger>, obtaining - * equivalent functionality to the Python interface in a Java paradigm. + * Base class for Jython bytearray (and bytes in due course) that provides + * most of the Java API, including Java {@link List} behaviour. Attempts to modify the contents + * through this API will throw a TypeError if the actual type of the object is not + * mutable. It is possible for a Java client to treat this class as a + * List<PyInteger>, obtaining equivalent functionality to the Python interface in a + * Java paradigm. *

    * Subclasses must define (from {@link PySequence}): *

      @@ -29,11 +29,29 @@ *
    • {@link #delRange(int, int)}
    • *
    * since the default implementations will otherwise throw an exception. + *

    + * Many of the methods implemented here are inherited or thinly wrapped by {@link PyByteArray}, + * which offers them as Java API, or exposes them as Python methods. These prototype Python methods + * mostly accept a {@link PyObject} as argument, where you might have expected a byte[] + * or BaseBytes, in order to accommodate the full range of types accepted by the Python + * equivalent: usually, any PyObject that implements {@link BufferProtocol}, providing + * a one-dimensional array of bytes, is an acceptable argument. In the documentation, the reader + * will often see the terms "byte array" or "object viewable as bytes" instead of + * BaseBytes when this broader scope is intended. + *

    + * Where the methods return a BaseBytes, this is will normally be an instance of the + * class of the object on which the method was actually called. For example {@link #capitalize()}, + * defined in BaseBytes to return a BaseBytes, actually returns a {@link PyByteArray} + * when applied to a bytearray. Or it may be that the method returns a + * PyList of instances of the target type, for example {@link #rpartition(PyObject)}. + * This is achieved by the sub-class defining {@link #getslice(int, int, int)} and + * {@link #getBuilder(int)} to return instances of its own type. See the documentation of particular + * methods for more information. */ public abstract class BaseBytes extends PySequence implements List { /** - * Simple constructor of empty zero-length array of defined type. + * Constructs a zero-length BaseBytes of explicitly-specified sub-type. * * @param type explicit Jython type */ @@ -44,7 +62,7 @@ } /** - * Simple constructor of zero-filled array of defined size and type. + * Constructs a zero-filled array of defined size and type. * * @param size required * @param type explicit Jython type @@ -56,7 +74,7 @@ } /** - * Construct byte array of defined type by copying values from int[]. + * Constructs a byte array of defined type by copying values from int[]. * * @param type explicit Jython type * @param value source of values (and size) @@ -72,8 +90,8 @@ } /** - * Construct byte array of defined type by copying character values from a String. These values - * have to be in the Python byte range 0 to 255. + * Constructs a byte array of defined type by copying character values from a String. These + * values have to be in the Python byte range 0 to 255. * * @param type explicit Jython type * @param value source of characters @@ -250,7 +268,8 @@ * @param encoding name of optional encoding * @param errors name of optional errors policy * @return encoded string - * @throws PyException(TypeError) if the PyString is actually a PyUnicode and encoding is null + * @throws PyException (TypeError) if the PyString is actually a {@link PyUnicode} + * and encoding is null */ protected static String encode(PyString arg, String encoding, String errors) throws PyException { // Jython encode emits a String (not byte[]) @@ -278,7 +297,7 @@ * * @param start index in this byte array at which the first character code lands * @param value source of characters - * @throws PyException(ValueError) if any value[i] > 255 + * @throws PyException (ValueError) if any value[i] > 255 */ protected void setBytes(int start, String value) throws PyException { int n = value.length(); @@ -294,7 +313,7 @@ * * @param start index in this byte array at which the first character code lands * @param value source of characters - * @throws PyException(ValueError) if any value[i] > 255 + * @throws PyException (ValueError) if any value[i] > 255 */ protected void setBytes(int start, int step, String value) throws PyException { int n = value.length(); @@ -307,7 +326,7 @@ /** * Helper for __new__ and __init__ and the Java API constructor from - * int in subclasses. Construct zero-filled bytearray of specified size. + * int in subclasses. Construct zero-filled byte array of specified size. * * @param n size of zero-filled array */ @@ -332,24 +351,11 @@ view.copyTo(storage, offset); } -// /** -// * Helper for the Java API constructor from a {@link #PyBuffer}. View is (perhaps) a stop-gap -// until -// * the Jython implementation of PEP 3118 (buffer API) is embedded. -// * -// * @param value a byte-oriented view -// */ -// void init(PyBuffer value) { -// int n = value.getLen(); -// newStorage(n); -// value.copyTo(storage, offset); -// } -// /** * Helper for __new__ and __init__ and the Java API constructor from - * bytearray or bytes in subclasses. + * bytearray or bytes in subclasses. * - * @param source bytearray (or bytes) to copy + * @param source bytearray (or bytes) to copy */ protected void init(BaseBytes source) { newStorage(source.size); @@ -429,8 +435,8 @@ * Load bytes into the container from the given iterable * * @param iter iterable source of values to enter into the container - * @throws PyException(TypeError) if any value not acceptable type - * @throws PyException(ValueError) if any value<0 or value>255 or string length!=1 + * @throws PyException (TypeError) if any value not acceptable type + * @throws PyException (ValueError) if any value<0 or value>255 or string length!=1 */ void loadFrom(Iterable iter) throws PyException { @@ -535,7 +541,7 @@ * Check that an index is within the range of the array, that is 0<=index<size. * * @param index to check - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ protected final void indexCheck(int index) throws PyException { if (index < 0 || index >= size) { @@ -569,7 +575,7 @@ * 0..255.) * * @param value to convert. - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (ValueError) if value<0 or value>255 */ protected static final byte byteCheck(int value) throws PyException { if (value < 0 || value > 255) { @@ -584,7 +590,7 @@ * Python bytes run 0..255.) * * @param value to convert. - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (ValueError) if value<0 or value>255 */ protected static final byte byteCheck(PyInteger value) throws PyException { return byteCheck(value.asInt()); @@ -602,8 +608,8 @@ * * * @param value to convert. - * @throws PyException(TypeError) if not acceptable type - * @throws PyException(ValueError) if value<0 or value>255 or string length!=1 + * @throws PyException (TypeError) if not acceptable type + * @throws PyException (ValueError) if value<0 or value>255 or string length!=1 */ protected static final byte byteCheck(PyObject value) throws PyException { if (value.isIndex()) { @@ -627,7 +633,7 @@ * if the return value is not null. * * @param b object to wrap - * @return byte-oriented view or null + * @return byte-oriented view or null */ protected static PyBuffer getView(PyObject b) { @@ -673,6 +679,7 @@ * API for org.python.core.PySequence * ============================================================================================ */ + @Override protected PyInteger pyget(int index) { return new PyInteger(intAt(index)); } @@ -681,8 +688,10 @@ * We're not implementing these here, but we can give a stronger guarantee about the return type * and save some casting and type anxiety. */ + @Override protected abstract BaseBytes getslice(int start, int stop, int step); + @Override protected abstract BaseBytes repeat(int count); /* @@ -696,9 +705,9 @@ * * @param index to insert at * @param element to insert (by value) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the subclass is immutable */ public void pyinsert(int index, PyObject element) { // This won't succeed: it just produces the right error. @@ -720,8 +729,8 @@ } /** - * Class defining the behaviour of bytearray with respect to slice assignment, etc., which - * differs from the default (list) behaviour in small ways. + * Class defining the behaviour of bytearray with respect to slice assignment, + * etc., which differs from the default (list) behaviour in small ways. */ private class IndexDelegate extends PySequence.DefaultIndexDelegate { @@ -886,11 +895,11 @@ } /** - * Implementation of __eq__ (equality) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __eq__ (equality) operator, capable of comparison with another byte array. + * Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___eq__(PyObject other) { int cmp = basebytes_cmpeq(other); @@ -904,11 +913,11 @@ } /** - * Implementation of __ne__ (not equals) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __ne__ (not equals) operator, capable of comparison with another byte + * array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___ne__(PyObject other) { int cmp = basebytes_cmpeq(other); @@ -922,11 +931,11 @@ } /** - * Implementation of __lt__ (less than) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __lt__ (less than) operator, capable of comparison with another byte array. + * Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___lt__(PyObject other) { int cmp = basebytes_cmp(other); @@ -941,10 +950,10 @@ /** * Implementation of __le__ (less than or equal to) operator, capable of comparison with another - * byte array or bytes. Comparison with an invalid type returns null. + * byte array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___le__(PyObject other) { int cmp = basebytes_cmp(other); @@ -959,10 +968,10 @@ /** * Implementation of __ge__ (greater than or equal to) operator, capable of comparison with - * another byte array or bytes. Comparison with an invalid type returns null. + * another byte array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___ge__(PyObject other) { int cmp = basebytes_cmp(other); @@ -977,10 +986,10 @@ /** * Implementation of __gt__ (greater than) operator, capable of comparison with another byte - * array or bytes. Comparison with an invalid type returns null. + * array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___gt__(PyObject other) { int cmp = basebytes_cmp(other); @@ -1031,7 +1040,7 @@ * @param ostart of slice to search. * @param oend of slice to search. * @param endswith true if we are doing endswith, false if startswith. - * @return true if and only if this bytearray ends with (one of) target. + * @return true if and only if this byte array ends with (one of) target. */ protected final synchronized boolean basebytes_starts_or_endswith(PyObject target, PyObject ostart, PyObject oend, boolean endswith) { @@ -1151,7 +1160,7 @@ * error policy. The returned PyObject will usually be a PyUnicode, but in practice * it is whatever the decode method of the codec decides. * - * @param encoding the name of the codec (uses default codec if null) + * @param encoding the name of the codec (uses default codec if null) * @return object containing the decoded characters */ public PyObject decode(String encoding) { @@ -1163,8 +1172,8 @@ * policy. The returned PyObject will usually be a PyUnicode, but in practice it is * whatever the decode method of the codec decides. * - * @param encoding the name of the codec (uses default codec if null) - * @param errors the name of the error policy (uses 'strict' if null) + * @param encoding the name of the codec (uses default codec if null) + * @param errors the name of the error policy (uses 'strict' if null) * @return object containing the decoded characters */ public PyObject decode(String encoding, String errors) { @@ -1210,6 +1219,7 @@ * * @return PyTuple that is first stage in pickling byte array */ + @Override public PyObject __reduce__() { return basebytes___reduce__(); } @@ -1555,6 +1565,7 @@ * the table is only 32 elements long, rather than (as it might be) 256 bytes, the number of * distinct byte values. */ + @Override protected int[] calculateSkipTable() { int[] skipTable = new int[MASK + 1]; int m = pattern.getLen(); @@ -1572,6 +1583,7 @@ * * @return the new effective end of the text */ + @Override public int currIndex() { return right + pattern.getLen() - 1; } @@ -1583,6 +1595,7 @@ * * @return matching index or -1 if no (further) occurrences found */ + @Override public int nextIndex() { int m = pattern.getLen(); @@ -1855,7 +1868,7 @@ * * @param result to receive the decoded values * @param hex specification of the bytes - * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered + * @throws PyException (ValueError) if non-hex characters, or isolated ones, are encountered */ static void basebytes_fromhex(BaseBytes result, String hex) throws PyException { @@ -1937,7 +1950,7 @@ // Unsuitable object to be in this join String fmt = "can only join an iterable of bytes (item %d has type '%.80s')"; throw Py.TypeError(String.format(fmt, iterList.size(), o.getType() - .fastGetName())); + .fastGetName())); } iterList.add(v); totalSize += v.getLen(); @@ -1990,6 +2003,9 @@ * the part before the separator, the separator itself, and the part after the separator. If the * separator is not found, return a 3-tuple containing the string itself, followed by two empty * byte arrays. + *

    + * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2000,6 +2016,9 @@ /** * Ready-to-expose implementation of Python partition(sep). + *

    + * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2032,6 +2051,9 @@ /** * Construct return value for implementation of Python partition(sep) or * rpartition(sep), returns [0:p], [p:q], [q:] + *

    + * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param p start of separator * @param q start of tail @@ -2430,6 +2452,9 @@ * containing the part before the separator, the separator itself, and the part after the * separator. If the separator is not found, return a 3-tuple containing two empty byte arrays, * followed by the byte array itself. + *

    + * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2440,6 +2465,9 @@ /** * Ready-to-expose implementation of Python rpartition(sep). + *

    + * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2471,6 +2499,9 @@ /** * Implementation of Python rsplit(), that returns a list of the words in the byte * array, using whitespace as the delimiter. See {@link #rsplit(PyObject, int)}. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return PyList of byte arrays that result from the split */ @@ -2480,10 +2511,13 @@ /** * Implementation of Python rsplit(sep), that returns a list of the words in the - * byte array, using sep as the delimiter. See {@link #rsplit(PyObject, int)} for the semantics - * of the separator. + * byte array, using sep as the delimiter. See {@link #rsplit(PyObject, int)} for + * the semantics of the separator. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @return PyList of byte arrays that result from the split */ public PyList rsplit(PyObject sep) { @@ -2492,22 +2526,26 @@ /** * Implementation of Python rsplit(sep, maxsplit), that returns a list of the words - * in the byte array, using sep as the delimiter. If maxsplit is given, at most maxsplit splits - * are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep as the delimiter. If maxsplit is + * given, at most maxsplit splits are done (thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is + * no limit on the number of splits (all possible splits are made). *

    - * The semantics of sep and maxcount are identical to those of split(sep, maxsplit) - * , except that splits are generated from the right (and pushed onto the front of the result - * list). The result is only different from that of split if maxcount - * limits the number of splits. For example, + * The semantics of sep and maxcount are identical to those of + * split(sep, maxsplit) , except that splits are generated from the right (and + * pushed onto the front of the result list). The result is only different from that of + * split if maxcount limits the number of splits. For example, *

      *
    • bytearray(b' 1 2 3 ').rsplit() returns * [bytearray(b'1'), bytearray(b'2'), bytearray(b'3')], and
    • *
    • bytearray(b' 1 2 3 ').rsplit(None, 1) returns * [bytearray(b' 1 2'), bytearray(b'3')]
    • . *
    + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2517,10 +2555,13 @@ /** * Ready-to-expose implementation of Python rsplit(sep, maxsplit), that returns a - * list of the words in the byte array, using sep as the delimiter. Use the defines whitespace - * semantics if sep is null. + * list of the words in the byte array, using sep as the delimiter. Use the defines + * whitespace semantics if sep is null. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2534,11 +2575,15 @@ /** * Implementation of Python rsplit(sep, maxsplit), that returns a list of the words - * in the byte array, using sep (which is not null) as the delimiter. If maxsplit>=0, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If - * maxsplit<0, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep (which is not null) as the delimiter. + * If maxsplit>=0, at most maxsplit splits are done (thus, the list + * will have at most maxsplit+1 elements). If maxsplit<0, then + * there is no limit on the number of splits (all possible splits are made). + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2589,15 +2634,18 @@ /** * Implementation of Python rsplit(None, maxsplit), that returns a list of the - * words in the byte array, using whitespace as the delimiter. If maxsplit is given, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit - * is not specified, then there is no limit on the number of splits (all possible splits are - * made). + * words in the byte array, using whitespace as the delimiter. If maxsplit is + * given, at most maxsplit splits are done (thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is no limit on + * the number of splits (all possible splits are made). *

    * Runs of consecutive whitespace are regarded as a single separator, and the result will * contain no empty strings at the start or end if the string has leading or trailing * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace - * with a None separator returns []. + * with a None separator returns []. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this/self. * * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split @@ -2648,6 +2696,9 @@ /** * Implementation of Python split(), that returns a list of the words in the byte * array, using whitespace as the delimiter. See {@link #split(PyObject, int)}. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return PyList of byte arrays that result from the split */ @@ -2657,10 +2708,13 @@ /** * Implementation of Python split(sep), that returns a list of the words in the - * byte array, using sep as the delimiter. See {@link #split(PyObject, int)} for the semantics - * of the separator. + * byte array, using sep as the delimiter. See {@link #split(PyObject, int)} for + * the semantics of the separator. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @return PyList of byte arrays that result from the split */ public PyList split(PyObject sep) { @@ -2669,29 +2723,33 @@ /** * Implementation of Python split(sep, maxsplit), that returns a list of the words - * in the byte array, using sep as the delimiter. If maxsplit is given, at most maxsplit splits - * are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep as the delimiter. If maxsplit is + * given, at most maxsplit splits are done. (Thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is + * no limit on the number of splits (all possible splits are made). *

    - * If sep is given, consecutive delimiters are not grouped together and are deemed to delimit - * empty strings (for example, '1,,2'.split(',') returns ['1', '', '2']). The sep argument may - * consist of multiple characters (for example, '1<>2<>3'.split('<>') returns ['1', - * '2', '3']). Splitting an empty string with a specified separator returns ['']. + * If sep is given, consecutive delimiters are not grouped together and are deemed + * to delimit empty strings (for example, '1,,2'.split(',') returns + * ['1', '', '2']). The sep argument may consist of multiple + * characters (for example, '1<>2<>3'.split('<>') returns ['1', + * '2', '3']). Splitting an empty string with a specified separator ['']. *

    - * If sep is not specified or is None, a different splitting algorithm is applied: runs of - * consecutive whitespace are regarded as a single separator, and the result will contain no - * empty strings at the start or end if the string has leading or trailing whitespace. - * Consequently, splitting an empty string or a string consisting of just whitespace with a None - * separator returns []. For example, - * + * If sep is not specified or is None, a different splitting algorithm + * is applied: runs of consecutive whitespace are regarded as a single separator, and the result + * will contain no empty strings at the start or end if the string has leading or trailing + * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace + * with a None separator returns []. For example, *

      *
    • bytearray(b' 1 2 3 ').split() returns * [bytearray(b'1'), bytearray(b'2'), bytearray(b'3')], and
    • *
    • bytearray(b' 1 2 3 ').split(None, 1) returns * [bytearray(b'1'), bytearray(b'2 3 ')].
    • *
    + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2701,10 +2759,13 @@ /** * Ready-to-expose implementation of Python split(sep, maxsplit), that returns a - * list of the words in the byte array, using sep as the delimiter. Use the defines whitespace - * semantics if sep is null. + * list of the words in the byte array, using sep as the delimiter. Use the defines + * whitespace semantics if sep is null. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2718,11 +2779,15 @@ /** * Implementation of Python split(sep, maxsplit), that returns a list of the words - * in the byte array, using sep (which is not null) as the delimiter. If maxsplit>=0, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If - * maxsplit<0, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep (which is not null) as the delimiter. + * If maxsplit>=0, at most maxsplit splits are done (thus, the list + * will have at most maxsplit+1 elements). If maxsplit<0, then + * there is no limit on the number of splits (all possible splits are made). + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2763,14 +2828,18 @@ /** * Implementation of Python split(None, maxsplit), that returns a list of the words - * in the byte array, using whitespace as the delimiter. If maxsplit is given, at most maxsplit - * splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using whitespace as the delimiter. If maxsplit is given, at + * most maxsplit splits are done (thus, the list will have at most maxsplit+1 + * elements). If maxsplit is not specified, then there is no limit on the number of + * splits (all possible splits are made). *

    * Runs of consecutive whitespace are regarded as a single separator, and the result will * contain no empty strings at the start or end if the string has leading or trailing * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace - * with a None separator returns []. + * with a None separator returns []. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split @@ -2815,6 +2884,9 @@ /** * Implementation of Python splitlines(), returning a list of the lines in the byte * array, breaking at line boundaries. Line breaks are not included in the resulting segments. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return List of segments */ @@ -2826,6 +2898,9 @@ * Implementation of Python splitlines(keepends), returning a list of the lines in * the string, breaking at line boundaries. Line breaks are not included in the resulting list * unless keepends is true. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param keepends if true, include the end of line bytes(s) * @return PyList of segments @@ -2838,6 +2913,9 @@ * Ready-to-expose implementation of Python splitlines(keepends), returning a list * of the lines in the array, breaking at line boundaries. Line breaks are not included in the * resulting list unless keepends is given and true. + *

    + * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param keepends if true, include the end of line bytes(s) * @return List of segments @@ -2888,7 +2966,7 @@ * byte. * * @param function name - * @param fillchar or null + * @param fillchar or null * @return */ protected static byte fillByteCheck(String function, String fillchar) { @@ -2937,7 +3015,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_center(int width, String fillchar) { @@ -2958,7 +3036,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_ljust(int width, String fillchar) { @@ -2977,7 +3055,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_rjust(int width, String fillchar) { @@ -3323,7 +3401,8 @@ /** * Java API equivalent of Python capitalize(). This method treats the bytes as * Unicode pont codes and is consistent with Java's {@link Character#toUpperCase(char)} and - * {@link Character#toLowerCase(char)}. + * {@link Character#toLowerCase(char)}. The BaseBytes returned by this method has + * the same actual type as this/self. * * @return a copy of the array with its first character capitalized and the rest lowercased. */ @@ -3332,7 +3411,9 @@ } /** - * Ready-to-expose implementation of Python capitalize(). + * Ready-to-expose implementation of Python capitalize(). The + * BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with its first character capitalized and the rest lowercased. */ @@ -3365,7 +3446,9 @@ /** * Java API equivalent of Python lower(). This method treats the bytes as Unicode - * pont codes and is consistent with Java's {@link Character#toLowerCase(char)}. + * pont codes and is consistent with Java's {@link Character#toLowerCase(char)}. The + * BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with all the cased characters converted to lowercase. */ @@ -3374,7 +3457,8 @@ } /** - * Ready-to-expose implementation of Python lower(). + * Ready-to-expose implementation of Python lower(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with all the cased characters converted to lowercase. */ @@ -3397,7 +3481,8 @@ /** * Java API equivalent of Python swapcase(). This method treats the bytes as * Unicode pont codes and is consistent with Java's {@link Character#toUpperCase(char)} and - * {@link Character#toLowerCase(char)}. + * {@link Character#toLowerCase(char)}. The BaseBytes returned by this method has + * the same actual type as this/self. * * @return a copy of the array with uppercase characters converted to lowercase and vice versa. */ @@ -3406,7 +3491,8 @@ } /** - * Ready-to-expose implementation of Python swapcase(). + * Ready-to-expose implementation of Python swapcase(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with uppercase characters converted to lowercase and vice versa. */ @@ -3432,7 +3518,8 @@ * Java API equivalent of Python title(). The algorithm uses a simple * language-independent definition of a word as groups of consecutive letters. The definition * works in many contexts but it means that apostrophes in contractions and possessives form - * word boundaries, which may not be the desired result. + * word boundaries, which may not be the desired result. The BaseBytes returned by + * this method has the same actual type as this/self. * * @return a titlecased version of the array where words start with an uppercase character and * the remaining characters are lowercase. @@ -3442,7 +3529,8 @@ } /** - * Ready-to-expose implementation of Python title(). + * Ready-to-expose implementation of Python title(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a titlecased version of the array where words start with an uppercase character and * the remaining characters are lowercase. @@ -3481,7 +3569,8 @@ /** * Java API equivalent of Python upper(). Note that * x.upper().isupper() might be false if the array contains uncased - * characters. + * characters. The BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with all the cased characters converted to uppercase. */ @@ -3490,7 +3579,8 @@ } /** - * Ready-to-expose implementation of Python upper(). + * Ready-to-expose implementation of Python upper(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with all the cased characters converted to uppercase. */ @@ -3533,7 +3623,7 @@ * * @param index of value in byte array * @return the integer value at the index - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ public synchronized int intAt(int index) throws PyException { indexCheck(index); @@ -3599,7 +3689,7 @@ */ private static final void appendHexEscape(StringBuilder buf, int c) { buf.append("\\x").append(Character.forDigit((c & 0xf0) >> 4, 16)) - .append(Character.forDigit(c & 0xf, 16)); + .append(Character.forDigit(c & 0xf, 16)); } /** @@ -3666,8 +3756,8 @@ */ /** - * Access to the bytearray (or bytes) as a {@link java.util.List}. The List interface supplied - * by BaseBytes delegates to this object. + * Access to the byte array as a {@link java.util.List}. The List interface supplied by + * BaseBytes delegates to this object. */ protected final List listDelegate = new AbstractList() { @@ -3689,9 +3779,9 @@ * Replaces the element at the specified position in this list with the specified element. * * @see java.util.AbstractList#set(int, java.lang.Object) - * @throws PyException(TypeError) if actual class is immutable - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if actual class is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 */ @Override public PyInteger set(int index, PyInteger element) throws PyException { @@ -3707,9 +3797,9 @@ * currently at that position and any subsequent elements to the right. * * @see java.util.AbstractList#add(int, java.lang.Object) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the owning concrete subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the owning concrete subclass is immutable */ @Override public void add(int index, PyInteger element) throws PyException { @@ -3724,7 +3814,7 @@ * removed from the list. * * @see java.util.AbstractList#remove(int) - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ @Override public PyInteger remove(int index) { @@ -3737,11 +3827,12 @@ }; /** - * Number of bytes in bytearray (or bytes) object. + * Number of bytes in bytearray (or bytes) object. * * @see java.util.List#size() * @return Number of bytes in byte array. * */ + @Override public int size() { return size; } @@ -3749,6 +3840,7 @@ /* * @see java.util.List#isEmpty() */ + @Override public boolean isEmpty() { return size == 0; } @@ -3757,6 +3849,7 @@ * Returns true if this list contains the specified value. More formally, returns true if and * only if this list contains at least one integer e such that o.equals(PyInteger(e)). */ + @Override public boolean contains(Object o) { return listDelegate.contains(o); } @@ -3764,6 +3857,7 @@ /* * @see java.util.List#iterator() */ + @Override public Iterator iterator() { return listDelegate.iterator(); } @@ -3771,6 +3865,7 @@ /* * @see java.util.List#toArray() */ + @Override public Object[] toArray() { return listDelegate.toArray(); } @@ -3778,6 +3873,7 @@ /* * @see java.util.List#toArray(T[]) */ + @Override public T[] toArray(T[] a) { return listDelegate.toArray(a); } @@ -3785,6 +3881,7 @@ /* * @see java.util.List#add(java.lang.Object) */ + @Override public boolean add(PyInteger o) { return listDelegate.add(o); } @@ -3792,6 +3889,7 @@ /* * @see java.util.List#remove(java.lang.Object) */ + @Override public boolean remove(Object o) { return listDelegate.remove(o); } @@ -3799,6 +3897,7 @@ /* * @see java.util.List#containsAll(java.util.Collection) */ + @Override public boolean containsAll(Collection c) { return listDelegate.containsAll(c); } @@ -3806,6 +3905,7 @@ /* * @see java.util.List#addAll(java.util.Collection) */ + @Override public boolean addAll(Collection c) { return listDelegate.addAll(c); } @@ -3813,6 +3913,7 @@ /* * @see java.util.List#addAll(int, java.util.Collection) */ + @Override public boolean addAll(int index, Collection c) { return listDelegate.addAll(index, c); } @@ -3820,6 +3921,7 @@ /* * @see java.util.List#removeAll(java.util.Collection) */ + @Override public boolean removeAll(Collection c) { return listDelegate.removeAll(c); } @@ -3827,6 +3929,7 @@ /* * @see java.util.List#retainAll(java.util.Collection) */ + @Override public boolean retainAll(Collection c) { return listDelegate.retainAll(c); } @@ -3834,6 +3937,7 @@ /* * @see java.util.List#clear() */ + @Override public void clear() { listDelegate.clear(); } @@ -3865,6 +3969,7 @@ /* * @see java.util.List#hashCode() */ + @Override public int hashCode() { return listDelegate.hashCode(); } @@ -3872,6 +3977,7 @@ /* * @see java.util.List#get(int) */ + @Override public PyInteger get(int index) { return listDelegate.get(index); } @@ -3879,6 +3985,7 @@ /* * @see java.util.List#set(int, java.lang.Object) */ + @Override public PyInteger set(int index, PyInteger element) { return listDelegate.set(index, element); } @@ -3886,6 +3993,7 @@ /* * @see java.util.List#add(int, java.lang.Object) */ + @Override public void add(int index, PyInteger element) { listDelegate.add(index, element); } @@ -3893,6 +4001,7 @@ /* * @see java.util.List#remove(int) */ + @Override public PyInteger remove(int index) { return listDelegate.remove(index); } @@ -3900,6 +4009,7 @@ /* * @see java.util.List#indexOf(java.lang.Object) */ + @Override public int indexOf(Object o) { return listDelegate.indexOf(o); } @@ -3907,6 +4017,7 @@ /* * @see java.util.List#lastIndexOf(java.lang.Object) */ + @Override public int lastIndexOf(Object o) { return listDelegate.lastIndexOf(o); } @@ -3914,6 +4025,7 @@ /* * @see java.util.List#listIterator() */ + @Override public ListIterator listIterator() { return listDelegate.listIterator(); } @@ -3921,6 +4033,7 @@ /* * @see java.util.List#listIterator(int) */ + @Override public ListIterator listIterator(int index) { return listDelegate.listIterator(index); } @@ -3928,6 +4041,7 @@ /* * @see java.util.List#subList(int, int) */ + @Override public List subList(int fromIndex, int toIndex) { return listDelegate.subList(fromIndex, toIndex); } @@ -3986,8 +4100,8 @@ * It is intended the client call this method only once to get the result of a series of * append operations. A second call to {@link #getCount()}, before any further appending, * returns a zero-length array. This is to ensure that the same array is not given out - * twice. However, {@link #getCount()} continues to return the number bytes accumuated until - * an append next occurs. + * twice. However, {@link #getCount()} continues to return the number bytes accumulated + * until an append next occurs. * * @return an array containing the accumulated result */ @@ -4131,7 +4245,7 @@ * Every sub-class of BaseBytes overrides this method to return a Builder<B> * where B is (normally) that class's particular type, and it extends * Builder<B> so that {@link Builder#getResult()} produces an instance of - * B from the contents.. + * B from the contents. * * @param capacity of the Builder<B> returned * @return a Builder<B> for the correct sub-class diff --git a/src/org/python/core/PyBUF.java b/src/org/python/core/PyBUF.java --- a/src/org/python/core/PyBUF.java +++ b/src/org/python/core/PyBUF.java @@ -3,20 +3,20 @@ /** * This interface provides a base for the key interface of the buffer API, {@link PyBuffer}, * including symbolic constants used by the consumer of a PyBuffer to specify its - * requirements and assumptions. The Jython buffer API emulates the CPython buffer API. + * requirements and assumptions. The Jython buffer API emulates the CPython buffer API. There are + * two reasons for separating parts of PyBuffer into this interface: *

      - *
    • There are two reasons for separating parts of PyBuffer into this interface: The - * constants defined in CPython have the names PyBUF_SIMPLE, + *
    • The constants defined in CPython have the names PyBUF_SIMPLE, * PyBUF_WRITABLE, etc., and the trick of defining ours here means we can write - * {@link PyBUF#SIMPLE}, {@link PyBUF#WRITABLE}, etc. so source code looks similar.
    • + * PyBUF.SIMPLE, PyBUF.WRITABLE, etc. so source code looks similar. *
    • It is not so easy in Java as it is in C to treat a byte array as storing * anything other than byte, and we prepare for the possibility of buffers with a * series of different primitive types by defining here those methods that would be in common - * between (Byte)Buffer and an assumed future FloatBuffer or + * between (Byte)Buffer and an assumed future FloatBuffer or * TypedBuffer<T>. (Compare java.nio.Buffer.)
    • *
    - * Except for other interfaces, it is unlikely any classes would implement PyBUF - * directly. Users of the Jython buffer API can mostly overlook the distinction and just use + * It is unlikely any classes would implement PyBUF, except indirectly through other + * interfaces. Users of the Jython buffer API can mostly overlook the distinction and just use * PyBuffer. */ public interface PyBUF { @@ -39,12 +39,12 @@ /** * An array reporting the size of the buffer, considered as a multidimensional array, in each - * dimension and (by its length) number of dimensions. The size is the size in "items". An item - * is the amount of buffer content addressed by one index or set of indices. In the simplest - * case an item is a single unit (byte), and there is one dimension. In complex cases, the array - * is multi-dimensional, and the item at each location is multi-unit (multi-byte). The consumer - * must not modify this array. A valid shape array is always returned (difference - * from CPython). + * dimension and (by its length) giving the number of dimensions. The size of the buffer is its + * size in "items". An item is the amount of buffer content addressed by one index or set of + * indices. In the simplest case an item is a single unit (byte), and there is one dimension. In + * complex cases, the array is multi-dimensional, and the item at each location is multi-unit + * (multi-byte). The consumer must not modify this array. A valid shape array is + * always returned (difference from CPython). * * @return the dimensions of the buffer as an array */ @@ -59,7 +59,7 @@ /** * The total number of units (bytes) stored, which will be the product of the elements of the - * shape array, and the item size. + * shape array, and the item size in units. * * @return the total number of units stored. */ @@ -81,13 +81,13 @@ /** * The suboffsets array is a further part of the support for interpreting the * buffer as an n-dimensional array of items, where the array potentially uses indirect - * addressing (like a real Java array of arrays, in fact). This is only applicable when there - * are more than 1 dimension and works in conjunction with the strides array. (More + * addressing (like a real Java array of arrays, in fact). This is only applicable when there is + * more than 1 dimension, and it works in conjunction with the strides array. (More * on this in the CPython documentation.) When used, suboffsets[k] is an integer - * index, bit a byte offset as in CPython. The consumer must not modify this array. When not + * index, not a byte offset as in CPython. The consumer must not modify this array. When not * needed for navigation null is returned (as in CPython). * - * @return suboffsets array or null in not necessary for navigation + * @return suboffsets array or null if not necessary for navigation */ int[] getSuboffsets(); @@ -108,15 +108,15 @@ static final int MAX_NDIM = 64; /** * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to - * specify that it expects to write to the buffer contents. getBuffer will raise an exception if - * the exporter's buffer cannot meet this requirement. + * specify that it expects to write to the buffer contents. getBuffer will raise an + * exception if the exporter's buffer cannot meet this requirement. */ static final int WRITABLE = 0x0001; /** * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to * specify that it assumes a simple one-dimensional organisation of the exported storage with - * item size of one. getBuffer will raise an exception if the consumer sets this flag and the - * exporter's buffer cannot be navigated that simply. + * item size of one. getBuffer will raise an exception if the consumer sets this + * flag and the exporter's buffer cannot be navigated that simply. */ static final int SIMPLE = 0; /** @@ -162,7 +162,7 @@ * specify that it will assume a contiguous organisation of the units, but will enquire which * organisation it actually is. * - * getBuffer will raise an exception if the exporter's buffer is not contiguous. + * getBuffer will raise an exception if the exporter's buffer is not contiguous. * ANY_CONTIGUOUS implies STRIDES. */ // Further CPython strangeness since it uses the strides array to answer the enquiry. @@ -216,7 +216,7 @@ /* Constants for readability, not standard for CPython */ /** - * Field mask, use as in if ((flags&NAVIGATION) == STRIDES) .... The importance of + * Field mask, used as in if ((flags&NAVIGATION) == STRIDES) .... The importance of * the subset of flags defined by this mask is not so much in their "navigational" character as * in the way they are treated in a buffer request. *

    @@ -243,7 +243,7 @@ */ static final int IS_F_CONTIGUOUS = F_CONTIGUOUS & ~STRIDES; /** - * Field mask, use as in if ((flags&CONTIGUITY)== ... ) .... + * Field mask, used as in if ((flags&CONTIGUITY)== ... ) .... */ static final int CONTIGUITY = (C_CONTIGUOUS | F_CONTIGUOUS | ANY_CONTIGUOUS) & ~STRIDES; -} \ No newline at end of file +} diff --git a/src/org/python/core/PyBuffer.java b/src/org/python/core/PyBuffer.java --- a/src/org/python/core/PyBuffer.java +++ b/src/org/python/core/PyBuffer.java @@ -163,9 +163,9 @@ * When a PyBuffer is the target, the same checks are carried out on the consumer * flags, and a return will normally be a reference to that buffer. A Jython * PyBuffer keeps count of these re-exports in order to match them with the number - * of calls to {@link #release()}. When the last matching release() arrives it is considered - * "final", and release actions may then take place on the exporting object. After the final - * release of a buffer, a call to getBuffer should raise an exception. + * of calls to {@link #release()}. When the last matching release() arrives it is + * considered "final", and release actions may then take place on the exporting object. After + * the final release of a buffer, a call to getBuffer should raise an exception. */ @Override PyBuffer getBuffer(int flags) throws PyException; @@ -213,16 +213,17 @@ * this.getPointer(i). A request for a slice where start = s, * length = N and stride = m, results in a buffer * y such that y(k) = x(s+km) where k=0..(N-1). In Python terms, this is - * the slice x[s : s+(N-1)m+1 : m] (if m>0) or the slice x[s : s+(N-1)m-1 : m] - * (if m<0). Implementations should check that this range is entirely within the current - * buffer. + * the slice x[s : s+(N-1)m+1 : m] (if m>0) or the slice x[s : s+(N-1)m-1 : + * m] (if m<0). Implementations should check that this range is entirely within + * the current buffer. *

    * In a simple buffer backed by a contiguous byte array, the result is a strided PyBuffer on the * same storage but where the offset is adjusted by s and the stride is as supplied. If * the current buffer is already strided and/or has an item size larger than single bytes, the - * new start index, length and stride will be translated from the arguments given, through this - * buffer's stride and item size. The consumer always expresses start and - * strides in terms of the abstract view of this buffer. + * new start index, length and stride will be translated + * from the arguments given, through this buffer's stride and item size. The caller always + * expresses start and strides in terms of the abstract view of this + * buffer. * * @param flags specifying features demanded and the navigational capabilities of the consumer * @param start index in the current buffer @@ -265,13 +266,11 @@ * Return a structure describing the slice of a byte array that holds the data being exported to * the consumer. For a one-dimensional contiguous buffer, assuming the following client code * where obj has type BufferProtocol: - * *

          * PyBuffer a = obj.getBuffer();
          * int itemsize = a.getItemsize();
          * PyBuffer.Pointer b = a.getBuf();
          * 
    - * * the item with index k is in the array b.storage at index * [b.offset + k*itemsize] to [b.offset + (k+1)*itemsize - 1] * inclusive. And if itemsize==1, the item is simply the byte @@ -287,17 +286,15 @@ PyBuffer.Pointer getBuf(); /** - * Return a structure describing the slice of a byte array that points to a single item from the - * data being exported to the consumer. For a one-dimensional contiguous buffer, assuming the + * Return a structure describing the position in a byte array of a single item from the data + * being exported to the consumer. For a one-dimensional contiguous buffer, assuming the * following client code where obj has type BufferProtocol: - * *
          * int k = ... ;
          * PyBuffer a = obj.getBuffer();
          * int itemsize = a.getItemsize();
          * PyBuffer.Pointer b = a.getPointer(k);
          * 
    - * * the item with index k is in the array b.storage at index * [b.offset] to [b.offset + itemsize - 1] inclusive. And if * itemsize==1, the item is simply the byte b.storage[b.offset] @@ -312,11 +309,10 @@ PyBuffer.Pointer getPointer(int index); /** - * Return a structure describing the slice of a byte array that points to a single item from the - * data being exported to the consumer, in the case that array may be multi-dimensional. For a + * Return a structure describing the position in a byte array of a single item from the data + * being exported to the consumer, in the case that array may be multi-dimensional. For a * 3-dimensional contiguous buffer, assuming the following client code where obj * has type BufferProtocol: - * *
          * int i, j, k;
          * // ... calculation that assigns i, j, k
    @@ -324,18 +320,17 @@
          * int itemsize = a.getItemsize();
          * PyBuffer.Pointer b = a.getPointer(i,j,k);
          * 
    - * * the item with index [i,j,k] is in the array b.storage at index * [b.offset] to [b.offset + itemsize - 1] inclusive. And if * itemsize==1, the item is simply the byte b.storage[b.offset] *

    * Essentially this is a method for computing the offset of a particular index. The client is * free to navigate the underlying buffer b.storage without respecting these - * boundaries. - *

    - * If the buffer is also non-contiguous, b.storage[b.offset] is still the (first - * byte of) the item at index [0,...,0]. However, it is necessary to navigate b - * using the shape, strides and sub-offsets provided by the API. + * boundaries. If the buffer is non-contiguous, the above description is still valid (since a + * multi-byte item must itself be contiguously stored), but in any additional navigation of + * b.storage[] to other units, the client must use the shape, strides and + * sub-offsets provided by the API. Normally one starts b = a.getBuf() in order to + * establish the offset of index [0,...,0]. * * @param indices multidimensional index at which to position the pointer * @return structure defining the byte[] slice that is the shared data diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -12,26 +12,24 @@ import org.python.expose.MethodType; /** - * Partial implementation of Python bytearray. At the present stage of development, the class - * provides: - *

      - *
    • constructors (both __init__ and the Java API constructors),
    • - *
    • the slice operations (get, set and delete)
    • - *
    • a List<PyInteger> implementation for the Java API
    • - *
    - * and this is founded on a particular approach to storage management internally. However, the - * implementation does not support the memoryview interface either for access or a a - * source for its constructors although the signatures are present. The rich set of string-like - * operations due a bytearray is not implemented. - * + * Implementation of Python bytearray with a Java API that includes equivalents to most + * of the Python API. These Python equivalents accept a {@link PyObject} as argument, where you + * might have expected a byte[] or PyByteArray, in order to accommodate + * the full range of types accepted by the Python equivalent: usually, any PyObject + * that implements {@link BufferProtocol}, providing a one-dimensional array of bytes, is an + * acceptable argument. In the documentation, the reader will often see the terms "byte array" or + * "object viewable as bytes" instead of bytearray when this broader scope is intended. + * This may relate to parameters, or to the target object itself (in text that applies equally to + * base or sibling classes). */ @ExposedType(name = "bytearray", base = PyObject.class, doc = BuiltinDocs.bytearray_doc) public class PyByteArray extends BaseBytes implements BufferProtocol { + /** The {@link PyType} of bytearray. */ public static final PyType TYPE = PyType.fromClass(PyByteArray.class); /** - * Create a zero-length Python bytearray of explicitly-specified sub-type + * Constructs a zero-length Python bytearray of explicitly-specified sub-type * * @param type explicit Jython type */ @@ -40,16 +38,16 @@ } /** - * Create a zero-length Python bytearray. + * Constructs a zero-length Python bytearray. */ public PyByteArray() { super(TYPE); } /** - * Create zero-filled Python bytearray of specified size. + * Constructs zero-filled Python bytearray of specified size. * - * @param size of bytearray + * @param size of bytearray */ public PyByteArray(int size) { super(TYPE); @@ -57,7 +55,7 @@ } /** - * Construct bytearray by copying values from int[]. + * Constructs a bytearray by copying values from int[]. * * @param value source of the bytes (and size) */ @@ -66,8 +64,8 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is a - * bytearray (or bytes). + * Constructs a new array filled exactly by a copy of the contents of the source, which is a + * bytearray (or bytes). * * @param value source of the bytes (and size) */ @@ -77,7 +75,7 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is a + * Constructs a new array filled exactly by a copy of the contents of the source, which is a * byte-oriented {@link PyBuffer}. * * @param value source of the bytes (and size) @@ -88,7 +86,7 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is an + * Constructs a new array filled exactly by a copy of the contents of the source, which is an * object supporting the Jython version of the PEP 3118 buffer API. * * @param value source of the bytes (and size) @@ -99,7 +97,7 @@ } /** - * Create a new array filled from an iterable of PyObject. The iterable must yield objects + * Constructs a new array filled from an iterable of PyObject. The iterable must yield objects * convertible to Python bytes (non-negative integers less than 256 or strings of length 1). * * @param value source of the bytes (and size) @@ -110,8 +108,8 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, the encoding must be explicitly specified. * * @param arg primary argument from which value is taken * @param encoding name of optional encoding (must be a string type) @@ -123,12 +121,12 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, the encoding must be explicitly specified. * * @param arg primary argument from which value is taken - * @param encoding name of optional encoding (may be null to select the default for this - * installation) + * @param encoding name of optional encoding (may be null to select the default for + * this installation) * @param errors name of optional errors policy */ public PyByteArray(PyString arg, String encoding, String errors) { @@ -137,8 +135,8 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, an exception is thrown saying that the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, an exception is thrown saying that the encoding must be explicitly specified. * * @param arg primary argument from which value is taken */ @@ -148,7 +146,8 @@ } /** - * Construct bytearray by re-using an array of byte as storage initialised by the client. + * Constructs a bytearray by re-using an array of byte as storage initialised by + * the client. * * @param storage pre-initialised with desired value: the caller should not keep a reference */ @@ -158,12 +157,13 @@ } /** - * Construct bytearray by re-using an array of byte as storage initialised by the client. + * Constructs a bytearray by re-using an array of byte as storage initialised by + * the client. * * @param storage pre-initialised with desired value: the caller should not keep a reference * @param size number of bytes actually used - * @throws IllegalArgumentException if the range [0:size] is not within the array bounds of - * the storage. + * @throws IllegalArgumentException if the range [0:size] is not within the array bounds of the + * storage. */ PyByteArray(byte[] storage, int size) { super(TYPE); @@ -171,28 +171,26 @@ } /** - * Create a new bytearray object from an arbitrary Python object according to the same rules as - * apply in Python to the bytearray() constructor: + * Constructs a new bytearray object from an arbitrary Python object according to + * the same rules as apply in Python to the bytearray() constructor: *
      - *
    • bytearray() Construct a zero-length bytearray (arg is null).
    • - *
    • bytearray(int) Construct a zero-initialized bytearray of the given length.
    • - *
    • bytearray(iterable_of_ints) Construct from iterable yielding integers in [0..255]
    • - *
    • bytearray(string [, encoding [, errors] ]) Construct from a text string, optionally using - * the specified encoding.
    • - *
    • bytearray(unicode, encoding [, errors]) Construct from a unicode string using the - * specified encoding.
    • - *
    • bytearray(bytes_or_bytearray) Construct as a mutable copy of bytes or existing bytearray - * object.
    • + *
    • bytearray() Construct a zero-length bytearray.
    • + *
    • bytearray(int) Construct a zero-initialized bytearray of the + * given length.
    • + *
    • bytearray(iterable_of_ints) Construct from iterable yielding integers in + * [0..255]
    • + *
    • bytearray(buffer) Construct by reading from any object implementing + * {@link BufferProtocol}, including str/bytes or another bytearray.
    • *
    * When it is necessary to specify an encoding, as in the Python signature - * bytearray(string, encoding[, errors]), use the constructor - * {@link #PyByteArray(PyString, String, String)}. If the PyString is actually a PyUnicode, an - * encoding must be specified, and using this constructor will throw an exception about that. + * bytearray(string, encoding [, errors]), use the constructor + * {@link #PyByteArray(PyString, String, String)}. If the PyString is actually a + * PyUnicode, an encoding must be specified, and using this constructor will throw + * an exception about that. * - * @param arg primary argument from which value is taken (may be null) - * @throws PyException in the same circumstances as bytearray(arg), TypeError for non-iterable, - * non-integer argument type, and ValueError if iterables do not yield byte [0..255] - * values. + * @param arg primary argument from which value is taken (may be null) + * @throws PyException (TypeError) for non-iterable, + * @throws PyException (ValueError) if iterables do not yield byte [0..255] values. */ public PyByteArray(PyObject arg) throws PyException { super(TYPE); @@ -284,18 +282,19 @@ } } - /* ============================================================================================ + /* + * ============================================================================================ * API for org.python.core.PySequence * ============================================================================================ */ /** - * Returns a slice of elements from this sequence as a PyByteArray. + * Returns a slice of elements from this sequence as a PyByteArray. * * @param start the position of the first element. * @param stop one more than the position of the last element. * @param step the step size. - * @return a PyByteArray corresponding the the given range of elements. + * @return a PyByteArray corresponding the the given range of elements. */ @Override protected synchronized PyByteArray getslice(int start, int stop, int step) { @@ -333,8 +332,8 @@ } /** - * Returns a PyByteArray that repeats this sequence the given number of times, as in the - * implementation of __mul__ for strings. + * Returns a PyByteArray that repeats this sequence the given number of times, as + * in the implementation of __mul__ for strings. * * @param count the number of times to repeat this. * @return this byte array repeated count times. @@ -347,8 +346,8 @@ } /** - * Replace the contents of this PyByteArray with the given number of repeats of the original - * contents, as in the implementation of __mul__ for strings. + * Replace the contents of this PyByteArray with the given number of repeats of the + * original contents, as in the implementation of __mul__ for strings. * * @param count the number of times to repeat this. */ @@ -357,35 +356,36 @@ } /** - * Sets the indexed element of the bytearray to the given value. This is an extension point - * called by PySequence in its implementation of {@link #__setitem__} It is guaranteed by - * PySequence that the index is within the bounds of the array. Any other clients calling - * pyset(int) must make the same guarantee. + * Sets the indexed element of the bytearray to the given value. This is an + * extension point called by PySequence in its implementation of {@link #__setitem__} It is + * guaranteed by PySequence that the index is within the bounds of the array. Any other clients + * calling pyset(int) must make the same guarantee. * * @param index index of the element to set. * @param value the value to set this element to. - * @throws PyException(AttributeError) if value cannot be converted to an integer - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (AttributeError) if value cannot be converted to an integer + * @throws PyException (ValueError) if value<0 or value>255 */ + @Override public synchronized void pyset(int index, PyObject value) throws PyException { storage[index + offset] = byteCheck(value); } /** - * Insert the element (interpreted as a Python byte value) at the given index. - * Python int, long and string types of length 1 are allowed. + * Insert the element (interpreted as a Python byte value) at the given index. Python + * int, long and str types of length 1 are allowed. * * @param index to insert at * @param element to insert (by value) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the subclass is immutable */ @Override public synchronized void pyinsert(int index, PyObject element) { // Open a space at the right location. storageReplace(index, 0, 1); - storage[offset+index] = byteCheck(element); + storage[offset + index] = byteCheck(element); } /** @@ -398,9 +398,10 @@ * of exactly that many elements (or convertible to such a sequence). *

    * When assigning from a sequence type or iterator, the sequence may contain arbitrary - * PyObjects, but acceptable ones are PyInteger, PyLong or PyString of length 1. If - * any one of them proves unsuitable for assignment to a Python bytarray element, an exception - * is thrown and this bytearray is unchanged. + * {@link PyObject}s, but acceptable ones are {@link PyInteger}, {@link PyLong} or + * {@link PyString} of length 1. If any one of them proves unsuitable for assignment to a Python + * bytearray element, an exception is thrown and this bytearray is + * unchanged. * *

          * a = bytearray(b'abcdefghijklmnopqrst')
    @@ -472,14 +473,14 @@
     
         /**
          * Sets the given range of elements according to Python slice assignment semantics from a
    -     * zero-filled bytearray of the given length.
    +     * zero-filled bytearray of the given length.
          *
          * @see #setslice(int, int, int, PyObject)
          * @param start the position of the first element.
          * @param stop one more than the position of the last element.
          * @param step the step size.
          * @param len number of zeros to insert consistent with the slice assignment
    -     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
    +     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
          */
         private void setslice(int start, int stop, int step, int len) throws PyException {
             if (step == 1) {
    @@ -501,14 +502,14 @@
     
         /**
          * Sets the given range of elements according to Python slice assignment semantics from a
    -     * PyString.
    +     * {@link PyString}.
          *
          * @see #setslice(int, int, int, PyObject)
          * @param start the position of the first element.
          * @param stop one more than the position of the last element.
          * @param step the step size.
          * @param value a PyString object consistent with the slice assignment
    -     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
    +     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
          */
         private void setslice(int start, int stop, int step, PyString value) throws PyException {
             String v = value.asString();
    @@ -536,7 +537,7 @@
          * @param stop one more than the position of the last element.
          * @param step the step size.
          * @param value an object supporting the buffer API consistent with the slice assignment
    -     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
    +     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
          */
         private void setslice(int start, int stop, int step, BufferProtocol value) throws PyException {
     
    @@ -547,7 +548,7 @@
             if (step == 1) {
                 // Delete this[start:stop] and open a space of the right size
                 storageReplace(start, stop - start, len);
    -            view.copyTo(storage, start+offset);
    +            view.copyTo(storage, start + offset);
     
             } else {
                 // This is an extended slice which means we are replacing elements
    @@ -564,14 +565,14 @@
     
         /**
          * Sets the given range of elements according to Python slice assignment semantics from a
    -     * bytearray (or bytes).
    +     * bytearray (or bytes).
          *
          * @see #setslice(int, int, int, PyObject)
          * @param start the position of the first element.
          * @param stop one more than the position of the last element.
          * @param step the step size.
    -     * @param value a bytearray (or bytes) object consistent with the slice assignment
    -     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
    +     * @param value a bytearray (or bytes) object consistent with the slice assignment
    +     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
          */
         private void setslice(int start, int stop, int step, BaseBytes value) throws PyException {
     
    @@ -603,14 +604,14 @@
     
         /**
          * Sets the given range of elements according to Python slice assignment semantics from a
    -     * bytearray (or bytes).
    +     * bytearray (or bytes).
          *
          * @see #setslice(int, int, int, PyObject)
          * @param start the position of the first element.
          * @param stop one more than the position of the last element.
          * @param step the step size.
          * @param iter iterable source of values to enter in the array
    -     * @throws PyException(SliceSizeError) if the iterable size is inconsistent with an extended
    +     * @throws PyException (SliceSizeError) if the iterable size is inconsistent with an extended
          *             slice
          */
         private void setslice(int start, int stop, int step, Iterable iter) {
    @@ -640,21 +641,12 @@
             }
         }
     
    -// Idiom:
    -// if (step == 1) {
    -// // Do something efficient with block start...stop-1
    -// } else {
    -// int n = sliceLength(start, stop, step);
    -// for (int i = start, j = 0; j < n; i += step, j++) {
    -// // Perform jth operation with element i
    -// }
    -// }
    -
         /*
          * Deletes an element from the sequence (and closes up the gap).
          *
          * @param index index of the element to delete.
          */
    +    @Override
         protected synchronized void del(int index) {
             // XXX Change SequenceIndexDelegate to avoid repeated calls to del(int) for extended slice
             storageDelete(index, 1);
    @@ -667,6 +659,7 @@
          *
          * @param stop one more than the position of the last element.
          */
    +    @Override
         protected synchronized void delRange(int start, int stop) {
             storageDelete(start, stop - start);
         }
    @@ -714,29 +707,29 @@
         }
     
         /**
    -     * Initialise a mutable bytearray object from various arguments. This single initialisation must
    -     * support:
    +     * Initialise a mutable bytearray object from various arguments. This single
    +     * initialisation must support:
          * 
      - *
    • bytearray() Construct a zero-length bytearray.
    • - *
    • bytearray(int) Construct a zero-initialized bytearray of the given length.
    • - *
    • bytearray(iterable_of_ints) Construct from iterable yielding integers in [0..255]
    • - *
    • bytearray(string [, encoding [, errors] ]) Construct from a text string, optionally using + *
    • bytearray() Construct a zero-length bytearray.
    • + *
    • bytearray(int) Construct a zero-initialized bytearray of the + * given length.
    • + *
    • bytearray(iterable_of_ints) Construct from iterable yielding integers in + * [0..255]
    • + *
    • bytearray(buffer) Construct by reading from any object implementing + * {@link BufferProtocol}, including str/bytes or another bytearray.
    • + *
    • bytearray(string, encoding [, errors]) Construct from a + * str/bytes, decoded using the system default encoding, and encoded to bytes using * the specified encoding.
    • - *
    • bytearray(unicode, encoding [, errors]) Construct from a unicode string using the - * specified encoding.
    • - *
    • bytearray(bytes_or_bytearray) Construct as a mutable copy of bytes or existing bytearray - * object.
    • + *
    • bytearray(unicode, encoding [, errors]) Construct from a + * unicode string, encoded to bytes using the specified encoding.
    • *
    - * Unlike CPython we are not able to support the initialisation:
  • bytearray(memory_view) - * Construct as copy of any object implementing the buffer API.
  • Although effectively - * a constructor, it is possible to call __init__ on a 'used' object so the method does not - * assume any particular prior state. + * Although effectively a constructor, it is possible to call __init__ on a 'used' + * object so the method does not assume any particular prior state. * * @param args argument array according to Jython conventions * @param kwds Keywords according to Jython conventions - * @throws PyException in the same circumstances as bytearray(arg), TypeError for non-iterable, - * non-integer argument type, and ValueError if iterables do not yield byte [0..255] - * values. + * @throws PyException (TypeError) for non-iterable, + * @throws PyException (ValueError) if iterables do not yield byte [0..255] values. */ @ExposedNew @ExposedMethod(doc = BuiltinDocs.bytearray___init___doc) @@ -795,7 +788,8 @@ }; } - /* ============================================================================================ + /* + * ============================================================================================ * Python API rich comparison operations * ============================================================================================ */ @@ -830,8 +824,6 @@ return basebytes___gt__(other); } - - @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.bytearray___eq___doc) final synchronized PyObject bytearray___eq__(PyObject other) { return basebytes___eq__(other); @@ -862,7 +854,8 @@ return basebytes___gt__(other); } -/* ============================================================================================ +/* + * ============================================================================================ * Python API for bytearray * ============================================================================================ */ @@ -876,10 +869,8 @@ final synchronized PyObject bytearray___add__(PyObject o) { PyByteArray sum = null; - // XXX re-write using buffer API - if (o instanceof BaseBytes) { BaseBytes ob = (BaseBytes)o; // Quick route: allocate the right size bytearray and copy the two parts in. @@ -985,7 +976,7 @@ * length 1. * * @param element the item to append. - * @throws PyException(ValueError) if element<0 or element>255 + * @throws PyException (ValueError) if element<0 or element>255 */ public void append(PyObject element) { bytearray_append(element); @@ -1002,9 +993,10 @@ * Implement to the standard Python __contains__ method, which in turn implements the * in operator. * - * @param o the element to search for in this bytearray. + * @param o the element to search for in this bytearray. * @return the result of the search. **/ + @Override public boolean __contains__(PyObject o) { return basebytes___contains__(o); } @@ -1038,7 +1030,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray center(int width, String fillchar) { @@ -1097,11 +1089,11 @@ * Implementation of Python endswith(suffix). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @return true if and only if this bytearray ends with the suffix (or one of them) + * @return true if and only if this bytearray ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix) { return basebytes_starts_or_endswith(suffix, null, null, true); @@ -1111,13 +1103,14 @@ * Implementation of Python endswith( suffix [, start ] ). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. With optional - * start (which may be null or Py.None), define the - * effective bytearray to be the slice [start:] of this bytearray. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. With + * optional start (which may be null or Py.None), define + * the effective bytearray to be the slice [start:] of this + * bytearray. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match + * @param start of slice in this bytearray to match * @return true if and only if this[start:] ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix, PyObject start) { @@ -1128,15 +1121,15 @@ * Implementation of Python endswith( suffix [, start [, end ]] ). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. With optional - * start and end (which may be null or - * Py.None), define the effective bytearray to be the slice - * [start:end] of this bytearray. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. With + * optional start and end (which may be null or + * Py.None), define the effective bytearray to be the slice + * [start:end] of this bytearray. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match - * @param end of slice in this bytearray to match + * @param start of slice in this bytearray to match + * @param end of slice in this bytearray to match * @return true if and only if this[start:end] ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix, PyObject start, PyObject end) { @@ -1180,7 +1173,7 @@ /** * Append the elements in the argument sequence to the end of the array, equivalent to: - * s[len(s):len(s)] = o. The argument must be a subclass of BaseBytes or an + * s[len(s):len(s)] = o. The argument must be a subclass of {@link BaseBytes} or an * iterable type returning elements compatible with byte assignment. * * @param o the sequence of items to append to the list. @@ -1192,7 +1185,7 @@ @ExposedMethod(doc = BuiltinDocs.bytearray_extend_doc) final synchronized void bytearray_extend(PyObject o) { // Use the general method, assigning to the crack at the end of the array. - // Note this deals with all legitimate PyObject types and the case o==this. + // Note this deals with all legitimate PyObject types including the case o==this. setslice(size, size, 1, o); } @@ -1253,7 +1246,7 @@ *
    * * @param hex specification of the bytes - * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered + * @throws PyException (ValueError) if non-hex characters, or isolated ones, are encountered */ static PyByteArray fromhex(String hex) throws PyException { return bytearray_fromhex(TYPE, hex); @@ -1322,9 +1315,11 @@ /** * This type is not hashable. + * + * @throws PyException (TypeError) as this type is not hashable. */ @Override - public int hashCode() { + public int hashCode() throws PyException { return bytearray___hash__(); } @@ -1443,10 +1438,10 @@ return (PyByteArray)basebytes_upper(); } - /** - * Implementation of Python join(iterable). Return a bytearray which is the - * concatenation of the byte arrays in the iterable iterable. The separator between - * elements is the byte array providing this method. + /** + * Implementation of Python join(iterable). Return a bytearray which + * is the concatenation of the byte arrays in the iterable iterable. The separator + * between elements is the byte array providing this method. * * @param iterable of byte array objects, or objects viewable as such. * @return byte array produced by concatenation. @@ -1484,7 +1479,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray ljust(int width, String fillchar) { @@ -1498,8 +1493,8 @@ } /** - * Implementation of Python lstrip(). Return a copy of the byte array with the leading - * whitespace characters removed. + * Implementation of Python lstrip(). Return a copy of the byte array with the + * leading whitespace characters removed. * * @return a byte array containing this value stripped of those bytes */ @@ -1510,10 +1505,10 @@ /** * Implementation of Python lstrip(bytes) * - * Return a copy of the byte array with the leading characters removed. The bytes - * argument is an object specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a prefix; - * rather, all combinations of its values are stripped. + * Return a copy of the byte array with the leading characters removed. The bytes argument is an + * object specifying the set of characters to be removed. If null or + * None, the bytes argument defaults to removing whitespace. The bytes argument is + * not a prefix; rather, all combinations of its values are stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (at the left) @@ -1542,7 +1537,8 @@ } /** - * Removes and return the last element in the byte array. + * Remove and return the last element in the byte array. + * * @return PyInteger representing the value */ public PyInteger pop() { @@ -1584,7 +1580,7 @@ * argument must be a PyInteger, PyLong or string of length 1. * * @param o the value to remove from the list. - * @throws PyException ValueError if o not found in bytearray + * @throws PyException ValueError if o not found in bytearray */ public void remove(PyObject o) throws PyException { bytearray_remove(o); @@ -1746,7 +1742,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray rjust(int width, String fillchar) { @@ -1793,7 +1789,8 @@ } /** - * Implementation of Python rstrip(). Return a copy of the byte array with the trailing whitespace characters removed. + * Implementation of Python rstrip(). Return a copy of the byte array with the + * trailing whitespace characters removed. * * @return a byte array containing this value stripped of those bytes (at right) */ @@ -1804,10 +1801,10 @@ /** * Implementation of Python rstrip(bytes) * - * Return a copy of the byte array with the trailing characters removed. The bytes - * argument is an object specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a suffix; - * rather, all combinations of its values are stripped. + * Return a copy of the byte array with the trailing characters removed. The bytes argument is + * an object specifying the set of characters to be removed. If null or + * None, the bytes argument defaults to removing whitespace. The bytes argument is + * not a suffix; rather, all combinations of its values are stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (at right) @@ -1844,11 +1841,12 @@ * Implementation of Python startswith(prefix). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @return true if and only if this bytearray starts with the prefix (or one of them) + * @return true if and only if this bytearray starts with the prefix (or one of + * them) */ public boolean startswith(PyObject prefix) { return basebytes_starts_or_endswith(prefix, null, null, false); @@ -1858,13 +1856,14 @@ * Implementation of Python startswith( prefix [, start ] ). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. With optional - * start (which may be null or Py.None), define the - * effective bytearray to be the slice [start:] of this bytearray. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. With + * optional start (which may be null or Py.None), define + * the effective bytearray to be the slice [start:] of this + * bytearray. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match + * @param start of slice in this bytearray to match * @return true if and only if this[start:] starts with the prefix (or one of them) */ public boolean startswith(PyObject prefix, PyObject start) { @@ -1875,15 +1874,15 @@ * Implementation of Python startswith( prefix [, start [, end ]] ). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. With optional - * start and end (which may be null or - * Py.None), define the effective bytearray to be the slice - * [start:end] of this bytearray. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. With + * optional start and end (which may be null or + * Py.None), define the effective bytearray to be the slice + * [start:end] of this bytearray. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match - * @param end of slice in this bytearray to match + * @param start of slice in this bytearray to match + * @param end of slice in this bytearray to match * @return true if and only if this[start:end] starts with the prefix (or one of them) */ public boolean startswith(PyObject prefix, PyObject start, PyObject end) { @@ -1896,8 +1895,8 @@ } /** - * Implementation of Python strip(). Return a copy of the byte array with the leading - * and trailing whitespace characters removed. + * Implementation of Python strip(). Return a copy of the byte array with the + * leading and trailing whitespace characters removed. * * @return a byte array containing this value stripped of those bytes (left and right) */ @@ -1909,9 +1908,10 @@ * Implementation of Python strip(bytes) * * Return a copy of the byte array with the leading and trailing characters removed. The bytes - * argument is anbyte arrayt specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a prefix or suffix; - * rather, all combinations of its values are stripped. + * argument is anbyte arrayt specifying the set of characters to be removed. If + * null or None, the bytes argument defaults to removing whitespace. + * The bytes argument is not a prefix or suffix; rather, all combinations of its values are + * stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (left and right) @@ -1980,13 +1980,13 @@ * Implementation of Python translate(table). * * Return a copy of the byte array where all bytes occurring in the optional argument - * deletechars are removed, and the remaining bytes have been mapped through the given - * translation table, which must be of length 256. + * deletechars are removed, and the remaining bytes have been mapped through the + * given translation table, which must be of length 256. * * @param table length 256 translation table (of a type that may be regarded as a byte array) * @return translated byte array */ - public PyByteArray translate(PyObject table) { + public PyByteArray translate(PyObject table) { return bytearray_translate(table, null); } @@ -1994,12 +1994,12 @@ * Implementation of Python translate(table[, deletechars]). * * Return a copy of the byte array where all bytes occurring in the optional argument - * deletechars are removed, and the remaining bytes have been mapped through the given - * translation table, which must be of length 256. + * deletechars are removed, and the remaining bytes have been mapped through the + * given translation table, which must be of length 256. * - * You can use the maketrans() helper function in the string module to create a translation - * table. For string objects, set the table argument to None for translations that only delete - * characters: + * You can use the Python maketrans() helper function in the string + * module to create a translation table. For string objects, set the table argument to + * None for translations that only delete characters: * * @param table length 256 translation table (of a type that may be regarded as a byte array) * @param deletechars object that may be regarded as a byte array, defining bytes to delete @@ -2131,6 +2131,7 @@ * * @param needed becomes the new value of this.size */ + @Override protected void newStorage(int needed) { if (needed > 0) { final int L = recLength(needed); @@ -2168,7 +2169,7 @@ * been preserved, although probably moved, and the gap between them has been adjusted to the * requested size. *

    - * The effect on this PyByteArray is that: + * The effect on this PyByteArray is that: * *

          * this.offset = f'
    @@ -2277,7 +2278,8 @@
          * where the regions of length a and b=size-(a+d) have been preserved
          * and the gap between them adjusted to specification. The new offset f' is chosen heuristically
          * by the method to optimise the efficiency of repeated adjustment near either end of the array,
    -     * e.g. repeated prepend or append operations. The effect on this PyByteArray is that:
    +     * e.g. repeated prepend or append operations. The effect on this PyByteArray is
    +     * that:
          *
          * 
          * this.offset = f'
    @@ -2357,7 +2359,8 @@
          * where the regions of length a and b=size-(a+d) have been preserved
          * and the gap between them adjusted to specification. The new offset f' is chosen heuristically
          * by the method to optimise the efficiency of repeated adjustment near either end of the array,
    -     * e.g. repeated prepend or append operations. The effect on this PyByteArray is that:
    +     * e.g. repeated prepend or append operations. The effect on this PyByteArray is
    +     * that:
          *
          * 
          * this.offset = f'
    @@ -2439,7 +2442,7 @@
          *
          * where the contents of region a have been preserved, although possbly moved, and
          * the gap at the end has the requested size. this method never shrinks the total storage. The
    -     * effect on this PyByteArray is that:
    +     * effect on this PyByteArray is that:
          *
          * 
          * this.offset = f or 0
    @@ -2518,7 +2521,7 @@
          * 
    * * where the regions of length a and b=size-(a+d) have been preserved - * and the gap between them eliminated. The effect on this PyByteArray is that: + * and the gap between them eliminated. The effect on this PyByteArray is that: * *
          * this.offset = f'
    @@ -2526,8 +2529,8 @@
          * 
    * * The method does not implement the Python repertoire of slice indices but avoids indexing - * outside the bytearray by silently adjusting a to be within it. Negative d is treated as 0 and - * if d is too large, it is truncated to the array end. + * outside the bytearray by silently adjusting a to be within it. Negative d is + * treated as 0 and if d is too large, it is truncated to the array end. * * @param a index of hole in byte array * @param d number to discard (will discard x[a,a+d-1]) @@ -2536,8 +2539,7 @@ private void storageDelete(int a, int d) { // storageReplace specialised for delete (e=0) - if (d == 0) - { + if (d == 0) { return; // Everything stays where it is. } @@ -2626,7 +2628,7 @@ * where the regions of length a and b=size-(a+e) have been preserved * and the e intervening elements reduced to e-d elements, by removing * exactly the elements with indices (relative to the start of valid data) a+k*c - * for k=0...d-1. The effect on this PyByteArray is that: + * for k=0...d-1. The effect on this PyByteArray is that: * *
          * this.offset = f'
    @@ -2634,8 +2636,8 @@
          * 
    * * The method does not implement the Python repertoire of slice indices but avoids indexing - * outside the bytearray by silently adjusting a to be within it. Negative d is treated as 0 and - * if d is too large, it is truncated to the array end. + * outside the bytearray by silently adjusting a to be within it. Negative d is + * treated as 0 and if d is too large, it is truncated to the array end. * * @param a index of hole in byte array * @param c (>0) step size between the locations of elements to delete @@ -2647,4 +2649,3 @@ // XXX Change SequenceIndexDelegate to use (and PyList to implement) delslice() } } - diff --git a/src/org/python/core/buffer/BaseBuffer.java b/src/org/python/core/buffer/BaseBuffer.java --- a/src/org/python/core/buffer/BaseBuffer.java +++ b/src/org/python/core/buffer/BaseBuffer.java @@ -22,7 +22,7 @@ * passed to the constructor. Otherwise, all methods for write access raise a * BufferError read-only exception and {@link #isReadonly()} returns true. * Sub-classes can follow the same pattern, setting {@link PyBUF#WRITABLE} in the constructor and, - * if they have to override the operations that write (storeAt and + * if they have to, overriding the operations that write (storeAt and * copyFrom). The recommended pattern is: * *
    @@ -31,9 +31,10 @@
      * }
      * // ... implementation of the write operation
      * 
    - * - * The implementors of simple buffers will find it efficient to override the generic access methods - * to which performance might be sensitive, with a calculation specific to their actual type. + * Another approach, used in the standard library, is to have distinct classes for the writable and + * read-only variants. The implementors of simple buffers will find it efficient to override the + * generic access methods to which performance might be sensitive, with a calculation specific to + * their actual type. *

    * At the time of writing, only one-dimensional buffers of item size one are used in the Jython * core. diff --git a/src/org/python/core/buffer/SimpleBuffer.java b/src/org/python/core/buffer/SimpleBuffer.java --- a/src/org/python/core/buffer/SimpleBuffer.java +++ b/src/org/python/core/buffer/SimpleBuffer.java @@ -48,6 +48,7 @@ * @throws ArrayIndexOutOfBoundsException if index0 and size are * inconsistent with storage.length */ + // XXX: "for sub-class use" = should be protected? public SimpleBuffer(byte[] storage, int index0, int size) throws PyException, ArrayIndexOutOfBoundsException { this(); @@ -90,6 +91,7 @@ * @param storage the array of bytes storing the implementation of the exporting object * @throws NullPointerException if storage is null */ + // XXX: "for sub-class use" = should be protected? public SimpleBuffer(byte[] storage) throws NullPointerException { this(); this.storage = storage; // Exported data (index0=0 from initialisation) diff --git a/src/org/python/core/buffer/SimpleStringBuffer.java b/src/org/python/core/buffer/SimpleStringBuffer.java --- a/src/org/python/core/buffer/SimpleStringBuffer.java +++ b/src/org/python/core/buffer/SimpleStringBuffer.java @@ -6,11 +6,11 @@ /** * Buffer API that appears to be a one-dimensional array of one-byte items providing read-only API, * but which is actually backed by a Java String. Some of the buffer API absolutely needs access to - * the data as a byte array (those parts that involve a {@link PyBuffer.Pointer} result), and therefore - * this class must create a byte array from the String for them. However, it defers creation of a - * byte array until that part of the API is actually used. Where possible, this class overrides - * those methods in SimpleBuffer that would otherwise access the byte array attribute to use the - * String instead. + * the data as a byte array (those parts that involve a PyBuffer.Pointer result), and + * therefore this class must create a byte array from the String for them. However, it defers + * creation of a byte array until that part of the API is actually used. Where possible, this class + * overrides those methods in SimpleBuffer that would otherwise access the byte array attribute to + * use the String instead. */ public class SimpleStringBuffer extends SimpleBuffer { diff --git a/src/org/python/core/buffer/Strided1DBuffer.java b/src/org/python/core/buffer/Strided1DBuffer.java --- a/src/org/python/core/buffer/Strided1DBuffer.java +++ b/src/org/python/core/buffer/Strided1DBuffer.java @@ -9,21 +9,24 @@ * properties in the usual way, designating a slice (or all) of a byte array, but also a * stride property (equal to getStrides()[0]). *

    - * Let this underlying buffer be the byte array u(i) for i=0..N-1, let x be the + * Let the underlying buffer be the byte array u(i) for i=0..N-1, let x be the * Strided1DBuffer, and let the stride be p. The storage works as follows. * Designate by x(j), for j=0..L-1, the byte at index j, that is, the byte - * retrieved by x.byteAt(j). Then, we store x(j) at u(a+pj), that is, - * x(0) is at u(a). When we construct such a buffer, we have to supply a = + * retrieved by x.byteAt(j). Thus, we store x(j) at u(a+pj), that is, + * x(0) = u(a). When we construct such a buffer, we have to supply a = * index0, L = length, and p = stride as the * constructor arguments. The last item in the slice x(L-1) is stored at u(a+p(L-1)). - * If p<0 and L>1, this will be to the left of u(a), so the constructor - * argument index0 is not then the low index of the range occupied by the data. Clearly both these - * indexes must be in the range 0 to N-1 inclusive, a rule enforced by the constructors + * For the simple case of positive stride, constructor argument index0 is the low index + * of the range occupied by the data. When the stride is negative, that is to say p<0, and + * L>1, this will be to the left of u(a), and the constructor argument + * index0 is not then the low index of the range occupied by the data. Clearly both + * these indexes must be in the range 0 to N-1 inclusive, a rule enforced by the constructors * (unless L=0, when it is assumed no array access will take place). *

    * The class may be used by exporters to create a strided slice (e.g. to export the diagonal of a * matrix) and in particular by other buffers to create strided slices of themselves, such as to - * create the memoryview that is returned as an extended slice of a memoryview. + * create the memoryview that is returned as an extended slice of a + * memoryview. */ public class Strided1DBuffer extends BaseBuffer { @@ -60,9 +63,14 @@ /** * Provide an instance of Strided1DBuffer with navigation variables initialised, - * for sub-class use. The buffer ( {@link #storage}, {@link #index0}), and the navigation ( + * for sub-class use. The buffer ({@link #storage}, {@link #index0}), and the navigation ( * {@link #shape} array and {@link #stride}) will be initialised from the arguments (which are * checked for range). + *

    + * The sub-class constructor should check that the intended access is compatible with this + * object by calling {@link #checkRequestFlags(int)}. (See the source of + * {@link Strided1DWritableBuffer#Strided1DWritableBuffer(int, byte[], int, int, int)} + * for an example of this use.) * * @param storage raw byte array containing exported data * @param index0 index into storage of item[0] @@ -72,6 +80,7 @@ * @throws ArrayIndexOutOfBoundsException if index0, length and * stride are inconsistent with storage.length */ + // XXX: "for sub-class use" = should be protected? public Strided1DBuffer(byte[] storage, int index0, int length, int stride) throws ArrayIndexOutOfBoundsException, NullPointerException { this(); diff --git a/src/org/python/core/buffer/Strided1DWritableBuffer.java b/src/org/python/core/buffer/Strided1DWritableBuffer.java --- a/src/org/python/core/buffer/Strided1DWritableBuffer.java +++ b/src/org/python/core/buffer/Strided1DWritableBuffer.java @@ -88,7 +88,7 @@ if (length > 0) { // Translate start relative to underlying buffer - int compStride= this.stride * stride; + int compStride = this.stride * stride; int compIndex0 = index0 + start * this.stride; // Construct a view, taking a lock on the root object (this or this.root) return new SlicedView(getRoot(), flags, storage, compIndex0, length, compStride); diff --git a/src/org/python/core/codecs.java b/src/org/python/core/codecs.java --- a/src/org/python/core/codecs.java +++ b/src/org/python/core/codecs.java @@ -919,11 +919,11 @@ * This method differs from the CPython equivalent (in Object/unicodeobject.c) * which works with an array of code points that are, in a wide build, Unicode code points. * - * @param unicode - * @param base64SetO - * @param base64WhiteSpace - * @param errors - * @return + * @param unicode to be encoded + * @param base64SetO true if characters in "set O" should be translated to base64 + * @param base64WhiteSpace true if white-space characters should be translated to base64 + * @param errors error policy name (e.g. "ignore", "replace") + * @return bytes representing the encoded unicode string */ public static String PyUnicode_EncodeUTF7(String unicode, boolean base64SetO, boolean base64WhiteSpace, String errors) { diff --git a/src/org/python/modules/SHA224Digest.java b/src/org/python/modules/SHA224Digest.java --- a/src/org/python/modules/SHA224Digest.java +++ b/src/org/python/modules/SHA224Digest.java @@ -1,6 +1,6 @@ package org.python.modules; -/** +/* * Copyright 2011 Gaurav Raje * Licensed to PSF under a Contributor Agreement. */ @@ -8,13 +8,14 @@ /** * SHA-224 as described in RFC 3874. This introduces the SHA224 Digest which has - * been ommitted from the JDK {@link java.security}. - * + * been omitted from the JDK java.security. * * This implementation has been borrowed from the Bouncy Castle implementation - * of SHA2 algorithms. Since they are MIT Licensed, they are compatible with + * of SHA2 algorithms. + * + * Since they are MIT Licensed, they are compatible with * this project. Their mandatory copyright notice follows. - * + *

      * Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle
      * (http://www.bouncycastle.org)
      * 
    @@ -35,6 +36,7 @@
      * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      * SOFTWARE.
    + * 
    */ public class SHA224Digest extends MessageDigest diff --git a/src/org/python/modules/_codecs.java b/src/org/python/modules/_codecs.java --- a/src/org/python/modules/_codecs.java +++ b/src/org/python/modules/_codecs.java @@ -314,12 +314,12 @@ * @param mapping to convert bytes to characters * @return decoded string and number of bytes consumed */ - public static PyTuple charmap_decode(String str, String errors, PyObject mapping) { + public static PyTuple charmap_decode(String bytes, String errors, PyObject mapping) { if (mapping == null || mapping == Py.None) { // Default to Latin-1 - return latin_1_decode(str, errors); + return latin_1_decode(bytes, errors); } else { - return charmap_decode(str, errors, mapping, false); + return charmap_decode(bytes, errors, mapping, false); } } @@ -1290,7 +1290,6 @@ * @param bytes to be decoded (Jython {@link PyString} convention) * @param errors error policy name (e.g. "ignore", "replace") * @param byteorder decoding "endianness" specified (in the Python -1, 0, +1 convention) - * @param isFinal if a "final" call, meaning the input must all be consumed * @return tuple (unicode_result, bytes_consumed, endianness) */ public static PyTuple utf_32_ex_decode(String bytes, String errors, int byteorder) { @@ -1335,7 +1334,6 @@ * @param order LE, BE or UNDEFINED (meaning bytes may begin with a byte order mark) * @param isFinal if a "final" call, meaning the input must all be consumed * @param findOrder if the returned tuple should include a report of the byte order - * @return tuple containing unicode result (as UTF-16 Java String) * @return tuple (unicode_result, bytes_consumed [, endianness]) */ private static PyTuple PyUnicode_DecodeUTF32Stateful(String bytes, String errors, diff --git a/src/org/python/modules/_io/OpenMode.java b/src/org/python/modules/_io/OpenMode.java --- a/src/org/python/modules/_io/OpenMode.java +++ b/src/org/python/modules/_io/OpenMode.java @@ -43,8 +43,7 @@ /** * Error message describing the way in which the mode is invalid, or null if no problem has been * found. This field may be set by the constructor (in the case of duplicate or unrecognised - * mode letters), by the {@link #isValid()} method, or by client code. A non-null value will - * cause {@link #isValid()} to return false. + * mode letters), by the {@link #validate()} method, or by client code. */ public String message; @@ -52,7 +51,7 @@ * Decode the given string to an OpenMode object, checking for duplicate or unrecognised mode * letters. Valid letters are those in "rwa+btU". Errors in the mode string do not raise an * exception, they simply generate an appropriate error message in {@link #message}. After - * construction, a client should always call {@link #isValid()} to complete validity checks. + * construction, a client should always call {@link #validate()} to complete validity checks. * * @param mode */ @@ -196,7 +195,7 @@ */ public void checkValid() throws PyException { - // Actually peform the check + // Actually perform the check validate(); // The 'other' flag reports alien symbols in the original mode string @@ -215,7 +214,7 @@ /** * The mode string we need when constructing a FileIO initialised with the present * mode. Note that this is not the same as the full open mode because it omits the text-based - * attributes, and not the same as {@link #raw()}. + * attributes. * * @return "r", "w", or "a" with optional "+". */ diff --git a/src/org/python/modules/_io/PyIOBase.java b/src/org/python/modules/_io/PyIOBase.java --- a/src/org/python/modules/_io/PyIOBase.java +++ b/src/org/python/modules/_io/PyIOBase.java @@ -160,7 +160,6 @@ * Truncate file to size bytes to the current position (as reported by * tell()). * - * @param size requested for stream (or null for default) * @return the new size */ public long truncate() { diff --git a/src/org/python/modules/_io/PyRawIOBase.java b/src/org/python/modules/_io/PyRawIOBase.java --- a/src/org/python/modules/_io/PyRawIOBase.java +++ b/src/org/python/modules/_io/PyRawIOBase.java @@ -187,7 +187,7 @@ * of bytes written. * * @param b buffer of bytes to be written - * @return + * @return the number of bytes written */ public PyObject write(PyObject b) { return _RawIOBase_write(b); diff --git a/src/org/python/util/ProxyCompiler.java b/src/org/python/util/ProxyCompiler.java --- a/src/org/python/util/ProxyCompiler.java +++ b/src/org/python/util/ProxyCompiler.java @@ -5,21 +5,22 @@ import org.python.core.PySystemState; public class ProxyCompiler { + /** * Compiles the python file by loading it - * - * FIXME: this is quite hackish right now. It basically starts a whole interpreter - * and set's proxyDebugDirectory as the destination for the compiled proxy class - * - * @param filename: python filename to exec - * @param destDir: destination directory for the proxy classes + * + * FIXME: this is quite hackish right now. It basically starts a whole interpreter and sets + * proxyDebugDirectory as the destination for the compiled proxy class + * + * @param filename python filename to exec + * @param destDir destination directory for the proxy classes */ public static void compile(String filename, String destDir) { Properties props = new Properties(System.getProperties()); props.setProperty(PySystemState.PYTHON_CACHEDIR_SKIP, "true"); PySystemState.initialize(props, null); PythonInterpreter interp = new PythonInterpreter(); - + String origProxyDir = org.python.core.Options.proxyDebugDirectory; try { org.python.core.Options.proxyDebugDirectory = destDir; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 15 22:46:10 2013 From: jython-checkins at python.org (alan.kennedy) Date: Fri, 15 Feb 2013 22:46:10 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=282=2E5=29=3A_Fixing_a_syntax?= =?utf-8?q?_error_hat_somehow_never_caused_an_error?= Message-ID: <3Z77MQ0Zg4z7Llb@mail.python.org> http://hg.python.org/jython/rev/1a672388105b changeset: 7050:1a672388105b branch: 2.5 parent: 7030:fa5bcc1c80cb user: Alan Kennedy date: Fri Feb 15 21:34:43 2013 +0000 summary: Fixing a syntax error hat somehow never caused an error files: Lib/socket.py | 2 +- NEWS | 1 + 2 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -308,7 +308,7 @@ 'htonl', 'ntohs', 'ntohl', 'inet_pton', 'inet_ntop', 'inet_aton', 'inet_ntoa', 'create_connection', 'socket', 'ssl', # exceptions - 'error', 'herror', 'gaierror', 'timeout', 'sslerror, + 'error', 'herror', 'gaierror', 'timeout', 'sslerror', # classes 'SocketType', # Misc flags diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Jython 2.5.4rc2 Bugs Fixed + - [ 1753 ] zlib doesn't call end() on compress and decompress - [ 1971 ] platform.py - 'NoneType' object has no attribute 'groups' - [ 1988 ] API for threading.condition fails to accept *args for acquire -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 15 22:46:11 2013 From: jython-checkins at python.org (alan.kennedy) Date: Fri, 15 Feb 2013 22:46:11 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?q?=3A_merge_w/2=2E5=3A_Fixing_a_syntax_error_that_somehow_never_c?= =?utf-8?q?aused_an_error?= Message-ID: <3Z77MR3DQzz7LmL@mail.python.org> http://hg.python.org/jython/rev/1bc65eccdbaa changeset: 7051:1bc65eccdbaa parent: 7049:1af38173c96d parent: 7050:1a672388105b user: Alan Kennedy date: Fri Feb 15 21:44:26 2013 +0000 summary: merge w/2.5: Fixing a syntax error that somehow never caused an error files: Lib/socket.py | 2 +- NEWS | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -337,7 +337,7 @@ 'htonl', 'ntohs', 'ntohl', 'inet_pton', 'inet_ntop', 'inet_aton', 'inet_ntoa', 'create_connection', 'socket', 'ssl', # exceptions - 'error', 'herror', 'gaierror', 'timeout', 'sslerror, + 'error', 'herror', 'gaierror', 'timeout', 'sslerror', # classes 'SocketType', # Misc flags diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -42,9 +42,7 @@ Jython 2.5.4rc2 Bugs Fixed - [ 1988 ] API for threading.condition fails to accept *args for acquire - -Jython 2.5.4rc2 - Bugs Fixed + - [ 1753 ] zlib doesn't call end() on compress and decompress - [ 1971 ] platform.py - 'NoneType' object has no attribute 'groups' - [ 1988 ] API for threading.condition fails to accept *args for acquire -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Feb 15 23:37:18 2013 From: jython-checkins at python.org (alan.kennedy) Date: Fri, 15 Feb 2013 23:37:18 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Updating_to_latest_platform?= =?utf-8?q?=2Epy_from?= Message-ID: <3Z78VQ4CwrzRNm@mail.python.org> http://hg.python.org/jython/rev/5a1c86290d31 changeset: 7052:5a1c86290d31 user: Alan Kennedy date: Fri Feb 15 22:34:43 2013 +0000 summary: Updating to latest platform.py from hg.python.org/cpython/file/0f7eec78569c/Lib/platform.py files: Lib/platform.py | 159 ++++++++++++++++++++++------------- 1 files changed, 99 insertions(+), 60 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py --- a/Lib/platform.py +++ b/Lib/platform.py @@ -34,6 +34,7 @@ # # # +# 1.0.7 - added DEV_NULL # 1.0.6 - added linux_distribution() # 1.0.5 - fixed Java support to allow running the module on Jython # 1.0.4 - added IronPython support @@ -110,14 +111,28 @@ """ -__version__ = '1.0.6' +__version__ = '1.0.7' import sys,string,os,re +### Globals & Constants if sys.platform.startswith("java"): from java.lang import System from org.python.core.Py import newString +# Determine the platform's /dev/null device +try: + DEV_NULL = os.devnull +except AttributeError: + # os.devnull was added in Python 2.4, so emulate it for earlier + # Python versions + if sys.platform in ('dos','win32','win16','os2'): + # Use the old CP/M NUL as device name + DEV_NULL = 'NUL' + else: + # Standard Unix uses /dev/null + DEV_NULL = '/dev/null' + ### Platform specific APIs _libc_search = re.compile(r'(__libc_init)' @@ -171,7 +186,7 @@ elif so: if lib != 'glibc': lib = 'libc' - if soversion > version: + if soversion and soversion > version: version = soversion if threads and version[-len(threads):] != threads: version = version + threads @@ -276,24 +291,6 @@ id = l[1] return '', version, id -def _test_parse_release_file(): - - for input, output in ( - # Examples of release file contents: - ('SuSE Linux 9.3 (x86-64)', ('SuSE Linux ', '9.3', 'x86-64')) - ('SUSE LINUX 10.1 (X86-64)', ('SUSE LINUX ', '10.1', 'X86-64')) - ('SUSE LINUX 10.1 (i586)', ('SUSE LINUX ', '10.1', 'i586')) - ('Fedora Core release 5 (Bordeaux)', ('Fedora Core', '5', 'Bordeaux')) - ('Red Hat Linux release 8.0 (Psyche)', ('Red Hat Linux', '8.0', 'Psyche')) - ('Red Hat Linux release 9 (Shrike)', ('Red Hat Linux', '9', 'Shrike')) - ('Red Hat Enterprise Linux release 4 (Nahant)', ('Red Hat Enterprise Linux', '4', 'Nahant')) - ('CentOS release 4', ('CentOS', '4', None)) - ('Rocks release 4.2.1 (Cydonia)', ('Rocks', '4.2.1', 'Cydonia')) - ): - parsed = _parse_release_file(input) - if parsed != output: - print (input, parsed) - def linux_distribution(distname='', version='', id='', supported_dists=_supported_dists, @@ -474,7 +471,16 @@ _ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) ' '.*' - 'Version ([\d.]+))') + '\[.* ([\d.]+)\])') + +# Examples of VER command output: +# +# Windows 2000: Microsoft Windows 2000 [Version 5.00.2195] +# Windows XP: Microsoft Windows XP [Version 5.1.2600] +# Windows Vista: Microsoft Windows [Version 6.0.6002] +# +# Note that the "Version" string gets localized on different +# Windows versions. def _syscmd_ver(system='', release='', version='', @@ -500,7 +506,7 @@ info = pipe.read() if pipe.close(): raise os.error,'command failed' - # XXX How can I supress shell errors from being written + # XXX How can I suppress shell errors from being written # to stderr ? except os.error,why: #print 'Command %s failed: %s' % (cmd,why) @@ -551,7 +557,7 @@ """ Get additional version information from the Windows Registry and return a tuple (version,csd,ptype) referring to version - number, CSD level and OS type (multi/single + number, CSD level (service pack), and OS type (multi/single processor). As a hint: ptype returns 'Uniprocessor Free' on single @@ -613,6 +619,7 @@ else: if csd[:13] == 'Service Pack ': csd = 'SP' + csd[13:] + if plat == VER_PLATFORM_WIN32_WINDOWS: regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion' # Try to guess the release name @@ -627,6 +634,7 @@ release = 'postMe' elif maj == 5: release = '2000' + elif plat == VER_PLATFORM_WIN32_NT: regkey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' if maj <= 4: @@ -668,8 +676,14 @@ release = '7' else: release = '2008ServerR2' + elif min == 2: + if product_type == VER_NT_WORKSTATION: + release = '8' + else: + release = '2012Server' else: - release = 'post2008Server' + release = 'post2012Server' + else: if not release: # E.g. Win3.1 with win32s @@ -759,6 +773,7 @@ 0x2: 'PowerPC', 0xa: 'i386'}.get(sysa,'') + versioninfo=('', '', '') return release,versioninfo,machine def _mac_ver_xml(): @@ -992,7 +1007,7 @@ # XXX Others too ? return default try: - f = os.popen('uname %s 2> /dev/null' % option) + f = os.popen('uname %s 2> %s' % (option, DEV_NULL)) except (AttributeError,os.error): return default output = string.strip(f.read()) @@ -1012,16 +1027,38 @@ case the command should fail. """ + + # We do the import here to avoid a bootstrap issue. + # See c73b90b6dadd changeset. + # + # [..] + # ranlib libpython2.7.a + # gcc -o python \ + # Modules/python.o \ + # libpython2.7.a -lsocket -lnsl -ldl -lm + # Traceback (most recent call last): + # File "./setup.py", line 8, in + # from platform import machine as platform_machine + # File "[..]/build/Lib/platform.py", line 116, in + # import sys,string,os,re,subprocess + # File "[..]/build/Lib/subprocess.py", line 429, in + # import select + # ImportError: No module named select + + import subprocess + if sys.platform in ('dos','win32','win16','os2'): # XXX Others too ? return default - target = _follow_symlinks(target).replace('"', '\\"') + target = _follow_symlinks(target) try: - f = os.popen('file "%s" 2> /dev/null' % target) + proc = subprocess.Popen(['file', target], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except (AttributeError,os.error): return default - output = string.strip(f.read()) - rc = f.close() + output = proc.communicate()[0] + rc = proc.wait() if not output or rc: return default else: @@ -1164,7 +1201,7 @@ node = _node() machine = '' - use_syscmd_ver = 01 + use_syscmd_ver = 1 # Try win32_ver() on win32 platforms if system == 'win32': @@ -1176,7 +1213,11 @@ # http://support.microsoft.com/kb/888731 and # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM if not machine: - machine = os.environ.get('PROCESSOR_ARCHITECTURE', '') + # WOW64 processes mask the native architecture + if "PROCESSOR_ARCHITEW6432" in os.environ: + machine = os.environ.get("PROCESSOR_ARCHITEW6432", '') + else: + machine = os.environ.get('PROCESSOR_ARCHITECTURE', '') if not processor: processor = os.environ.get('PROCESSOR_IDENTIFIER', machine) @@ -1216,10 +1257,6 @@ if not version: version = vendor - elif os.name == 'mac': - release,(version,stage,nonrel),machine = mac_ver() - system = 'MacOS' - # System specific extensions if system == 'OpenVMS': # OpenVMS seems to have release and version mixed up @@ -1339,6 +1376,11 @@ '(?: \(([\d\.]+)\))?' ' on (.NET [\d\.]+)') +_pypy_sys_version_parser = re.compile( + r'([\w.+]+)\s*' + '\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*' + '\[PyPy [^\]]+\]?') + _sys_version_cache = {} def _sys_version(sys_version=None): @@ -1400,6 +1442,16 @@ buildno = '' builddate = '' + elif "PyPy" in sys_version: + # PyPy + name = "PyPy" + match = _pypy_sys_version_parser.match(sys_version) + if match is None: + raise ValueError("failed to parse PyPy sys.version: %s" % + repr(sys_version)) + version, buildno, builddate, buildtime = match.groups() + compiler = "" + else: # CPython match = _sys_version_parser.match(sys_version) @@ -1409,15 +1461,16 @@ repr(sys_version)) version, buildno, builddate, buildtime, compiler = \ match.groups() - if hasattr(sys, 'subversion'): - # sys.subversion was added in Python 2.5 - name, branch, revision = sys.subversion - else: - name = 'CPython' - branch = '' - revision = '' + name = 'CPython' builddate = builddate + ' ' + buildtime + if hasattr(sys, 'subversion'): + # sys.subversion was added in Python 2.5 + _, branch, revision = sys.subversion + else: + branch = '' + revision = '' + # Add the patchlevel version if missing l = string.split(version, '.') if len(l) == 2: @@ -1429,29 +1482,15 @@ _sys_version_cache[sys_version] = result return result -def _test_sys_version(): - - _sys_version_cache.clear() - for input, output in ( - ('2.4.3 (#1, Jun 21 2006, 13:54:21) \n[GCC 3.3.4 (pre 3.3.5 20040809)]', - ('CPython', '2.4.3', '', '', '1', 'Jun 21 2006 13:54:21', 'GCC 3.3.4 (pre 3.3.5 20040809)')), - ('IronPython 1.0.60816 on .NET 2.0.50727.42', - ('IronPython', '1.0.60816', '', '', '', '', '.NET 2.0.50727.42')), - ('IronPython 1.0 (1.0.61005.1977) on .NET 2.0.50727.42', - ('IronPython', '1.0.0', '', '', '', '', '.NET 2.0.50727.42')), - ): - parsed = _sys_version(input) - if parsed != output: - print (input, parsed) - def python_implementation(): """ Returns a string identifying the Python implementation. Currently, the following implementations are identified: - 'CPython' (C implementation of Python), - 'IronPython' (.NET implementation of Python), - 'Jython' (Java implementation of Python). + 'CPython' (C implementation of Python), + 'IronPython' (.NET implementation of Python), + 'Jython' (Java implementation of Python), + 'PyPy' (Python implementation of Python). """ return _sys_version()[0] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 16 14:31:04 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 16 Feb 2013 14:31:04 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiBGaXggIzE4OTk6IEZp?= =?utf-8?q?x_to_platform=2Epy_to_correctly_avoid_a_warning_message_on_Wind?= =?utf-8?q?ows=2E?= Message-ID: <3Z7XKh56hQzQkj@mail.python.org> http://hg.python.org/jython/rev/ce225289358a changeset: 7053:ce225289358a branch: 2.5 parent: 7050:1a672388105b user: Alan Kennedy date: Sat Feb 16 13:13:40 2013 +0000 summary: Fix #1899: Fix to platform.py to correctly avoid a warning message on Windows. Updating NEWS files: Lib/platform.py | 3 ++- NEWS | 2 ++ 2 files changed, 4 insertions(+), 1 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py --- a/Lib/platform.py +++ b/Lib/platform.py @@ -803,7 +803,8 @@ """ Interface to the system's uname command. """ - if sys.platform in ('dos','win32','win16','os2'): + if sys.platform in ('dos','win32','win16','os2') or \ + (sys.platform.startswith('java') and os._name == 'nt'): # XXX Others too ? return default try: diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ Jython 2.5.4rc2 Bugs Fixed + - [ 1899 ] Fix to platform.py to correctly avoid a warning message on Windows. + - [ 1988 ] API for threading.condition fails to accept *args for acquire - [ 1753 ] zlib doesn't call end() on compress and decompress - [ 1971 ] platform.py - 'NoneType' object has no attribute 'groups' - [ 1988 ] API for threading.condition fails to accept *args for acquire -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 16 14:31:06 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 16 Feb 2013 14:31:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Updating_NEWS?= Message-ID: <3Z7XKk100FzQsw@mail.python.org> http://hg.python.org/jython/rev/0457c1a9b23c changeset: 7054:0457c1a9b23c parent: 7052:5a1c86290d31 user: Alan Kennedy date: Sat Feb 16 13:18:15 2013 +0000 summary: Updating NEWS files: Lib/platform.py | 2 +- NEWS | 1 + 2 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py --- a/Lib/platform.py +++ b/Lib/platform.py @@ -115,7 +115,7 @@ import sys,string,os,re -### Globals & Constants +### Globals & Constants if sys.platform.startswith("java"): from java.lang import System from org.python.core.Py import newString diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -41,6 +41,7 @@ Jython 2.5.4rc2 Bugs Fixed + - [ 1899 ] Fix to platform.py to correctly avoid a warning message on Windows. - [ 1988 ] API for threading.condition fails to accept *args for acquire - [ 1753 ] zlib doesn't call end() on compress and decompress - [ 1971 ] platform.py - 'NoneType' object has no attribute 'groups' -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Feb 16 14:31:07 2013 From: jython-checkins at python.org (alan.kennedy) Date: Sat, 16 Feb 2013 14:31:07 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?q?=3A_Disabling_a_test_dependent_on_sys=2Eexecutable=2C_which_is_?= =?utf-8?q?None_on_jython?= Message-ID: <3Z7XKl3SZXzQkj@mail.python.org> http://hg.python.org/jython/rev/d1cdccf5daa8 changeset: 7055:d1cdccf5daa8 parent: 7054:0457c1a9b23c parent: 7053:ce225289358a user: Alan Kennedy date: Sat Feb 16 13:29:22 2013 +0000 summary: Disabling a test dependent on sys.executable, which is None on jython files: Lib/platform.py | 3 ++- Lib/test/test_platform.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py --- a/Lib/platform.py +++ b/Lib/platform.py @@ -1003,7 +1003,8 @@ """ Interface to the system's uname command. """ - if sys.platform in ('dos','win32','win16','os2'): + if sys.platform in ('dos','win32','win16','os2') or \ + (sys.platform.startswith('java') and os._name == 'nt'): # XXX Others too ? return default try: diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -224,6 +224,8 @@ res = platform.dist() def test_libc_ver(self): + if sys.executable is None: + return import os if os.path.isdir(sys.executable) and \ os.path.exists(sys.executable+'.exe'): -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 18 09:01:00 2013 From: jython-checkins at python.org (jeff.allen) Date: Mon, 18 Feb 2013 09:01:00 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_counting_bug_in_=5Fio?= =?utf-8?q?=2EPyIOBase=2Ereadline?= Message-ID: <3Z8cvw6p46zM07@mail.python.org> http://hg.python.org/jython/rev/f6f92f896ffd changeset: 7056:f6f92f896ffd parent: 7049:1af38173c96d user: Jeff Allen date: Tue Feb 12 23:58:41 2013 +0000 summary: Fix counting bug in _io.PyIOBase.readline Discovered by test_memoryio test_readline. Now can remove skip. Also re-worked skip for test_detach to cite Issue 1996. files: Lib/test/test_memoryio.py | 20 ++++++------ src/org/python/modules/_io/PyIOBase.java | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -60,11 +60,6 @@ class MemoryTestMixin: - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_detach(self): buf = self.ioclass() self.assertRaises(self.UnsupportedOperation, buf.detach) @@ -179,11 +174,6 @@ memio.close() self.assertRaises(ValueError, memio.read) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_readline(self): buf = self.buftype("1234567890\n") memio = self.ioclass(buf * 2) @@ -413,6 +403,13 @@ UnsupportedOperation = pyio.UnsupportedOperation + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + @staticmethod def buftype(s): return s.encode("ascii") @@ -631,6 +628,9 @@ "array.array() does not have the new buffer API" )(PyBytesIOTest.test_bytes_array) + # Re-instate test_detach skipped by Jython in PyBytesIOTest + if support.is_jython: # FIXME: Jython issue 1996 + test_detach = MemoryTestMixin.test_detach # This test isn't working on Ubuntu on an Apple Intel powerbook, # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) diff --git a/src/org/python/modules/_io/PyIOBase.java b/src/org/python/modules/_io/PyIOBase.java --- a/src/org/python/modules/_io/PyIOBase.java +++ b/src/org/python/modules/_io/PyIOBase.java @@ -591,7 +591,7 @@ */ PyByteArray res = new PyByteArray(); - while (remainingLimit > 0) { + while (--remainingLimit >= 0) { /* * read() returns a str of one byte, doing at most one read to refill, or it returns -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 18 09:01:02 2013 From: jython-checkins at python.org (jeff.allen) Date: Mon, 18 Feb 2013 09:01:02 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_test=5Fmemoryio=2C_further_?= =?utf-8?q?test_skips_cite_Issue_1996=2E?= Message-ID: <3Z8cvy25fwzM07@mail.python.org> http://hg.python.org/jython/rev/89d306fc4d19 changeset: 7057:89d306fc4d19 user: Jeff Allen date: Wed Feb 13 00:37:49 2013 +0000 summary: test_memoryio, further test skips cite Issue 1996. files: Lib/test/test_memoryio.py | 11 +++++++++++ 1 files changed, 11 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -605,6 +605,13 @@ UnsupportedOperation = pyio.UnsupportedOperation EOF = "" + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + class PyStringIOPickleTest(TextIOTestMixin, unittest.TestCase): """Test if pickle restores properly the internal state of StringIO. @@ -676,6 +683,10 @@ # XXX: For the Python version of io.StringIO, this is highly # dependent on the encoding used for the underlying buffer. + # Re-instate test_detach skipped by Jython in PyBytesIOTest + if support.is_jython: # FIXME: Jython issue 1996 + test_detach = MemoryTestMixin.test_detach + # This test isn't working on Ubuntu on an Apple Intel powerbook, # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 18 09:01:03 2013 From: jython-checkins at python.org (jeff.allen) Date: Mon, 18 Feb 2013 09:01:03 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_unicode_exclusion_in_Py?= =?utf-8?q?ByteArray=2Esetslice?= Message-ID: <3Z8cvz4cj7zPkC@mail.python.org> http://hg.python.org/jython/rev/b50a4dc2fb15 changeset: 7058:b50a4dc2fb15 user: Jeff Allen date: Wed Feb 13 00:42:08 2013 +0000 summary: Fix unicode exclusion in PyByteArray.setslice Discovered by test_memoryio test_unicode that bytearray.extend(s) allows s to be unicode when it shouldn't. Now can remove skip. files: Lib/test/test_memoryio.py | 5 -- src/org/python/core/PyByteArray.java | 31 +++++++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -472,11 +472,6 @@ memio.seek(1, 1) self.assertEqual(memio.read(), buf[1:]) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_unicode(self): memio = self.ioclass() diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -502,7 +502,7 @@ /** * Sets the given range of elements according to Python slice assignment semantics from a - * {@link PyString}. + * {@link PyString} that is not a {@link PyUnicode}. * * @see #setslice(int, int, int, PyObject) * @param start the position of the first element. @@ -510,21 +510,28 @@ * @param step the step size. * @param value a PyString object consistent with the slice assignment * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice + * @throws PyException (ValueError) if the value is a PyUnicode */ private void setslice(int start, int stop, int step, PyString value) throws PyException { - String v = value.asString(); - int len = v.length(); - if (step == 1) { - // Delete this[start:stop] and open a space of the right size - storageReplace(start, stop - start, len); - setBytes(start, v); + if (value instanceof PyUnicode) { + // Has to be 8-bit PyString + throw Py.TypeError("can't set bytearray slice from unicode"); } else { - // This is an extended slice which means we are replacing elements - int n = sliceLength(start, stop, step); - if (n != len) { - throw SliceSizeError("bytes", len, n); + // Assignment is from 8-bit data + String v = value.asString(); + int len = v.length(); + if (step == 1) { + // Delete this[start:stop] and open a space of the right size + storageReplace(start, stop - start, len); + setBytes(start, v); + } else { + // This is an extended slice which means we are replacing elements + int n = sliceLength(start, stop, step); + if (n != len) { + throw SliceSizeError("bytes", len, n); + } + setBytes(start, step, v); } - setBytes(start, step, v); } } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 18 09:01:05 2013 From: jython-checkins at python.org (jeff.allen) Date: Mon, 18 Feb 2013 09:01:05 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Skip_tests_in_test=5Fio_req?= =?utf-8?q?uiring_os=2Epipe=28=29?= Message-ID: <3Z8cw10CDtzPlC@mail.python.org> http://hg.python.org/jython/rev/cc537214c5ec changeset: 7059:cc537214c5ec user: Jeff Allen date: Wed Feb 13 21:01:48 2013 +0000 summary: Skip tests in test_io requiring os.pipe() files: Lib/test/test_io.py | 5 ++--- 1 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2758,9 +2758,8 @@ pass - at unittest.skipIf(os.name == 'nt' or - (sys.platform[:4] == 'java' and os._name == 'nt'), - 'POSIX signals required for this test.') + at unittest.skipIf(support.is_jython, "Jython does not support os.pipe()") + at unittest.skipIf(os.name == 'nt', 'POSIX signals required for this test.') class SignalsTest(unittest.TestCase): def setUp(self): -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 18 09:01:06 2013 From: jython-checkins at python.org (jeff.allen) Date: Mon, 18 Feb 2013 09:01:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Remove_check_for_closed_fro?= =?utf-8?q?m_=5Fjyio=2ETextIOWrapper?= Message-ID: <3Z8cw23BvTzM07@mail.python.org> http://hg.python.org/jython/rev/e51ba816e845 changeset: 7060:e51ba816e845 user: Jeff Allen date: Sat Feb 16 09:50:29 2013 +0000 summary: Remove check for closed from _jyio.TextIOWrapper Only flush should be checked, and that now happens in PyIOBase. files: Lib/_jyio.py | 6 +----- Lib/test/test_memoryio.py | 5 ----- src/org/python/modules/_io/PyIOBase.java | 5 ++++- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -1042,22 +1042,18 @@ def seekable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self._seekable def readable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self.buffer.readable() def writable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self.buffer.writable() def flush(self): - self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation + self._checkInitialized() # Jython: to forbid use in an invalid state self.buffer.flush() self._telling = self._seekable diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -319,11 +319,6 @@ self.assertEqual(memio.flush(), None) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_flags(self): memio = self.ioclass() diff --git a/src/org/python/modules/_io/PyIOBase.java b/src/org/python/modules/_io/PyIOBase.java --- a/src/org/python/modules/_io/PyIOBase.java +++ b/src/org/python/modules/_io/PyIOBase.java @@ -181,7 +181,10 @@ } @ExposedMethod(doc = flush_doc) - final void _IOBase_flush() {} + final void _IOBase_flush() { + // Even types for which this remains a no-op must complain if closed (e.g. BytesIO) + _checkClosed(); + } /** * True if the object is closed to further client operations. It is the state accessed by -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 18 09:01:07 2013 From: jython-checkins at python.org (jeff.allen) Date: Mon, 18 Feb 2013 09:01:07 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Skip_test_and_add_new_in_te?= =?utf-8?q?st=5Fmemory=5Fio_test_of_unicode_tell=2E?= Message-ID: <3Z8cw35kLwzPl5@mail.python.org> http://hg.python.org/jython/rev/ebb67f175c9f changeset: 7061:ebb67f175c9f user: Jeff Allen date: Sun Feb 17 23:48:45 2013 +0000 summary: Skip test and add new in test_memory_io test of unicode tell. The test CStringIOTest.test_widechar fails because in Jython tell() does not return a position consistent with the character index. But this appears not to be required by the API. We skip it, and test seek/tell consistency, which the API requires. files: Lib/test/test_memoryio.py | 36 +++++++++++++++++++++++--- 1 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -677,11 +677,10 @@ if support.is_jython: # FIXME: Jython issue 1996 test_detach = MemoryTestMixin.test_detach - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") + # This test checks that tell() results are consistent with the length of + # text written, but this is not documented in the API: only that seek() + # accept what tell() returns. + @unittest.skipIf(support.is_jython, "Exact value of tell() is CPython specific") def test_widechar(self): buf = self.buftype("\U0002030a\U00020347") memio = self.ioclass(buf) @@ -694,6 +693,33 @@ self.assertEqual(memio.tell(), len(buf) * 2) self.assertEqual(memio.getvalue(), buf + buf) + # This test checks that seek() accepts what tell() returns, without requiring + # that tell() return a particular absolute value. Conceived for Jython, but + # probably universal. + def test_widechar_seek(self): + buf = self.buftype("\U0002030aX\u00ca\U00020347\u05d1Y\u0628Z") + memio = self.ioclass(buf) + self.assertEqual(memio.getvalue(), buf) + + # For each character in buf, read it back from memio and its tell value + chars = list(buf) + tells = list() + for ch in chars : + tells.append(memio.tell()) + self.assertEqual(memio.read(1), ch) + + # For each character in buf, seek to it and check it's there + chpos = zip(chars, tells) + chpos.reverse() + for ch, pos in chpos: + memio.seek(pos) + self.assertEqual(memio.read(1), ch) + + # Check write after seek to end + memio.seek(0, 2) + self.assertEqual(memio.write(buf), len(buf)) + self.assertEqual(memio.getvalue(), buf + buf) + # This test isn't working on Ubuntu on an Apple Intel powerbook, # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 18 09:01:09 2013 From: jython-checkins at python.org (jeff.allen) Date: Mon, 18 Feb 2013 09:01:09 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_fixes_from_test=5Fmemoryio?= Message-ID: <3Z8cw52SWjzPjq@mail.python.org> http://hg.python.org/jython/rev/a30708945630 changeset: 7062:a30708945630 parent: 7055:d1cdccf5daa8 parent: 7061:ebb67f175c9f user: Jeff Allen date: Mon Feb 18 08:00:09 2013 +0000 summary: Merge fixes from test_memoryio files: Lib/_jyio.py | 6 +- Lib/test/test_io.py | 5 +- Lib/test/test_memoryio.py | 77 ++++++++--- src/org/python/core/PyByteArray.java | 31 ++- src/org/python/modules/_io/PyIOBase.java | 7 +- 5 files changed, 79 insertions(+), 47 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -1042,22 +1042,18 @@ def seekable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self._seekable def readable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self.buffer.readable() def writable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self.buffer.writable() def flush(self): - self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation + self._checkInitialized() # Jython: to forbid use in an invalid state self.buffer.flush() self._telling = self._seekable diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2758,9 +2758,8 @@ pass - at unittest.skipIf(os.name == 'nt' or - (sys.platform[:4] == 'java' and os._name == 'nt'), - 'POSIX signals required for this test.') + at unittest.skipIf(support.is_jython, "Jython does not support os.pipe()") + at unittest.skipIf(os.name == 'nt', 'POSIX signals required for this test.') class SignalsTest(unittest.TestCase): def setUp(self): diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -60,11 +60,6 @@ class MemoryTestMixin: - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_detach(self): buf = self.ioclass() self.assertRaises(self.UnsupportedOperation, buf.detach) @@ -179,11 +174,6 @@ memio.close() self.assertRaises(ValueError, memio.read) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_readline(self): buf = self.buftype("1234567890\n") memio = self.ioclass(buf * 2) @@ -329,11 +319,6 @@ self.assertEqual(memio.flush(), None) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_flags(self): memio = self.ioclass() @@ -413,6 +398,13 @@ UnsupportedOperation = pyio.UnsupportedOperation + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + @staticmethod def buftype(s): return s.encode("ascii") @@ -475,11 +467,6 @@ memio.seek(1, 1) self.assertEqual(memio.read(), buf[1:]) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_unicode(self): memio = self.ioclass() @@ -608,6 +595,13 @@ UnsupportedOperation = pyio.UnsupportedOperation EOF = "" + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + class PyStringIOPickleTest(TextIOTestMixin, unittest.TestCase): """Test if pickle restores properly the internal state of StringIO. @@ -631,6 +625,9 @@ "array.array() does not have the new buffer API" )(PyBytesIOTest.test_bytes_array) + # Re-instate test_detach skipped by Jython in PyBytesIOTest + if support.is_jython: # FIXME: Jython issue 1996 + test_detach = MemoryTestMixin.test_detach # This test isn't working on Ubuntu on an Apple Intel powerbook, # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) @@ -676,11 +673,14 @@ # XXX: For the Python version of io.StringIO, this is highly # dependent on the encoding used for the underlying buffer. - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") + # Re-instate test_detach skipped by Jython in PyBytesIOTest + if support.is_jython: # FIXME: Jython issue 1996 + test_detach = MemoryTestMixin.test_detach + + # This test checks that tell() results are consistent with the length of + # text written, but this is not documented in the API: only that seek() + # accept what tell() returns. + @unittest.skipIf(support.is_jython, "Exact value of tell() is CPython specific") def test_widechar(self): buf = self.buftype("\U0002030a\U00020347") memio = self.ioclass(buf) @@ -693,6 +693,33 @@ self.assertEqual(memio.tell(), len(buf) * 2) self.assertEqual(memio.getvalue(), buf + buf) + # This test checks that seek() accepts what tell() returns, without requiring + # that tell() return a particular absolute value. Conceived for Jython, but + # probably universal. + def test_widechar_seek(self): + buf = self.buftype("\U0002030aX\u00ca\U00020347\u05d1Y\u0628Z") + memio = self.ioclass(buf) + self.assertEqual(memio.getvalue(), buf) + + # For each character in buf, read it back from memio and its tell value + chars = list(buf) + tells = list() + for ch in chars : + tells.append(memio.tell()) + self.assertEqual(memio.read(1), ch) + + # For each character in buf, seek to it and check it's there + chpos = zip(chars, tells) + chpos.reverse() + for ch, pos in chpos: + memio.seek(pos) + self.assertEqual(memio.read(1), ch) + + # Check write after seek to end + memio.seek(0, 2) + self.assertEqual(memio.write(buf), len(buf)) + self.assertEqual(memio.getvalue(), buf + buf) + # This test isn't working on Ubuntu on an Apple Intel powerbook, # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -502,7 +502,7 @@ /** * Sets the given range of elements according to Python slice assignment semantics from a - * {@link PyString}. + * {@link PyString} that is not a {@link PyUnicode}. * * @see #setslice(int, int, int, PyObject) * @param start the position of the first element. @@ -510,21 +510,28 @@ * @param step the step size. * @param value a PyString object consistent with the slice assignment * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice + * @throws PyException (ValueError) if the value is a PyUnicode */ private void setslice(int start, int stop, int step, PyString value) throws PyException { - String v = value.asString(); - int len = v.length(); - if (step == 1) { - // Delete this[start:stop] and open a space of the right size - storageReplace(start, stop - start, len); - setBytes(start, v); + if (value instanceof PyUnicode) { + // Has to be 8-bit PyString + throw Py.TypeError("can't set bytearray slice from unicode"); } else { - // This is an extended slice which means we are replacing elements - int n = sliceLength(start, stop, step); - if (n != len) { - throw SliceSizeError("bytes", len, n); + // Assignment is from 8-bit data + String v = value.asString(); + int len = v.length(); + if (step == 1) { + // Delete this[start:stop] and open a space of the right size + storageReplace(start, stop - start, len); + setBytes(start, v); + } else { + // This is an extended slice which means we are replacing elements + int n = sliceLength(start, stop, step); + if (n != len) { + throw SliceSizeError("bytes", len, n); + } + setBytes(start, step, v); } - setBytes(start, step, v); } } diff --git a/src/org/python/modules/_io/PyIOBase.java b/src/org/python/modules/_io/PyIOBase.java --- a/src/org/python/modules/_io/PyIOBase.java +++ b/src/org/python/modules/_io/PyIOBase.java @@ -181,7 +181,10 @@ } @ExposedMethod(doc = flush_doc) - final void _IOBase_flush() {} + final void _IOBase_flush() { + // Even types for which this remains a no-op must complain if closed (e.g. BytesIO) + _checkClosed(); + } /** * True if the object is closed to further client operations. It is the state accessed by @@ -591,7 +594,7 @@ */ PyByteArray res = new PyByteArray(); - while (remainingLimit > 0) { + while (--remainingLimit >= 0) { /* * read() returns a str of one byte, doing at most one read to refill, or it returns -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 25 10:08:18 2013 From: jython-checkins at python.org (alan.kennedy) Date: Mon, 25 Feb 2013 10:08:18 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_More_socket_decorator_conso?= =?utf-8?q?lidation?= Message-ID: <3ZDy4L3nF4z7LkL@mail.python.org> http://hg.python.org/jython/rev/3af5f760f5ea changeset: 7063:3af5f760f5ea user: Alan Kennedy date: Mon Feb 25 08:55:54 2013 +0000 summary: More socket decorator consolidation files: Lib/socket.py | 54 ++++++++++++-------------------------- 1 files changed, 17 insertions(+), 37 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -162,26 +162,24 @@ from functools import wraps # Used to map java exceptions to the equivalent python exception +# And to set the _last_error attribute on socket objects, to support SO_ERROR def raises_java_exception(method_or_function): @wraps(method_or_function) - def map_exception(*args, **kwargs): + def handle_exception(*args, **kwargs): + is_socket = (len(args) > 0 and isinstance(args[0], _nonblocking_api_mixin)) try: - return method_or_function(*args, **kwargs) - except java.lang.Exception, jlx: - raise _map_exception(jlx) - return map_exception - -# Used for SO_ERROR support. -def raises_error(method): - @wraps(method) - def set_last_error(obj, *args, **kwargs): - try: - setattr(obj, '_last_error', 0) - return method(obj, *args, **kwargs) + try: + return method_or_function(*args, **kwargs) + except java.lang.Exception, jlx: + raise _map_exception(jlx) except error, e: - setattr(obj, '_last_error', e[0]) + if is_socket: + setattr(args[0], '_last_error', e[0]) raise - return set_last_error + else: + if is_socket: + setattr(args[0], '_last_error', 0) + return handle_exception _feature_support_map = { 'ipv6': True, @@ -695,6 +693,7 @@ addrs.append(asPyString(addr.getHostAddress())) return (names, addrs) + at raises_java_exception def getfqdn(name=None): """ Return a fully qualified domain name for name. If name is omitted or empty @@ -1145,7 +1144,6 @@ def getblocking(self): return self.mode == MODE_BLOCKING - @raises_error @raises_java_exception def setsockopt(self, level, optname, value): if self.sock_impl: @@ -1168,7 +1166,6 @@ else: return self.pending_options.get( (level, optname), None) - @raises_error @raises_java_exception def shutdown(self, how): assert how in (SHUT_RD, SHUT_WR, SHUT_RDWR) @@ -1176,13 +1173,11 @@ raise error(errno.ENOTCONN, "Transport endpoint is not connected") self.sock_impl.shutdown(how) - @raises_error @raises_java_exception def close(self): if self.sock_impl: self.sock_impl.close() - @raises_error @raises_java_exception def getsockname(self): if self.sock_impl is None: @@ -1194,7 +1189,6 @@ raise error(errno.EINVAL, "Invalid argument") return self.sock_impl.getsockname() - @raises_error @raises_java_exception def getpeername(self): if self.sock_impl is None: @@ -1239,7 +1233,6 @@ return self.server return _nonblocking_api_mixin.getsockopt(self, level, optname) - @raises_error @raises_java_exception def bind(self, addr): assert not self.sock_impl @@ -1248,7 +1241,6 @@ _get_jsockaddr(addr, self.family, self.type, self.proto, AI_PASSIVE) self.local_addr = addr - @raises_error @raises_java_exception def listen(self, backlog): "This signifies a server socket" @@ -1258,7 +1250,6 @@ backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) self._config() - @raises_error @raises_java_exception def accept(self): "This signifies a server socket" @@ -1283,14 +1274,12 @@ self._config() # Configure timeouts, etc, now that the socket exists self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) - @raises_error @raises_java_exception def connect(self, addr): "This signifies a client socket" self._do_connect(addr) self._setup() - @raises_error @raises_java_exception def connect_ex(self, addr): "This signifies a client socket" @@ -1308,7 +1297,6 @@ self.istream = self.sock_impl.jsocket.getInputStream() self.ostream = self.sock_impl.jsocket.getOutputStream() - @raises_error @raises_java_exception def recv(self, n): if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') @@ -1326,12 +1314,10 @@ data = data[:m] return data.tostring() - @raises_error @raises_java_exception def recvfrom(self, n): - return self.recv(n), None + return self.recv(n), self.getpeername() - @raises_error @raises_java_exception def send(self, s): if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') @@ -1344,7 +1330,6 @@ sendall = send - @raises_error @raises_java_exception def close(self): if self.istream: @@ -1365,7 +1350,6 @@ def __init__(self): _nonblocking_api_mixin.__init__(self) - @raises_error @raises_java_exception def bind(self, addr): assert not self.sock_impl @@ -1385,19 +1369,16 @@ self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) self.connected = True - @raises_error @raises_java_exception def connect(self, addr): self._do_connect(addr) - @raises_error @raises_java_exception def connect_ex(self, addr): if not self.sock_impl: self._do_connect(addr) return 0 - @raises_error @raises_java_exception def sendto(self, data, p1, p2=None): if not p2: @@ -1417,7 +1398,6 @@ byte_array = java.lang.String(data).getBytes('iso-8859-1') return self.sock_impl.send(byte_array, flags) - @raises_error @raises_java_exception def recvfrom(self, num_bytes, flags=None): """ @@ -1437,7 +1417,6 @@ self._config() return self.sock_impl.recvfrom(num_bytes, flags) - @raises_error @raises_java_exception def recv(self, num_bytes, flags=None): if not self.sock_impl: @@ -1878,14 +1857,15 @@ self._out_buf.flush() return len(s) - @raises_java_exception def _get_server_cert(self): return self.java_ssl_socket.getSession().getPeerCertificates()[0] + @raises_java_exception def server(self): cert = self._get_server_cert() return cert.getSubjectDN().toString() + @raises_java_exception def issuer(self): cert = self._get_server_cert() return cert.getIssuerDN().toString() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Feb 25 10:08:19 2013 From: jython-checkins at python.org (alan.kennedy) Date: Mon, 25 Feb 2013 10:08:19 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Temporary_fix_for_=232016?= =?utf-8?q?=3A_ssl_sockets_have_broken_recv=28=29_and_makefile=28=29?= Message-ID: <3ZDy4M6WhGz7LkL@mail.python.org> http://hg.python.org/jython/rev/ae103c4e1aef changeset: 7064:ae103c4e1aef user: Alan Kennedy date: Mon Feb 25 09:06:24 2013 +0000 summary: Temporary fix for #2016: ssl sockets have broken recv() and makefile() files: Lib/socket.py | 9 ++++----- 1 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -1836,11 +1836,6 @@ java_ssl_socket.startHandshake() return java_ssl_socket - def __getattr__(self, attr_name): - if hasattr(self.jython_socket_wrapper, attr_name): - return getattr(self.jython_socket_wrapper, attr_name) - raise AttributeError(attr_name) - @raises_java_exception def read(self, n=4096): data = jarray.zeros(n, 'b') @@ -1851,12 +1846,16 @@ data = data[:m] return data.tostring() + recv = read + @raises_java_exception def write(self, s): self._out_buf.write(s) self._out_buf.flush() return len(s) + send = sendall = write + def _get_server_cert(self): return self.java_ssl_socket.getSession().getPeerCertificates()[0] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 26 10:25:49 2013 From: jython-checkins at python.org (alan.kennedy) Date: Tue, 26 Feb 2013 10:25:49 +0100 (CET) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uICgyLjUpOiAjMjAxNzoganl0aG9u?= =?utf-8?q?=2Ebat_script_pollutes_environment!_=28variables=29?= Message-ID: <3ZFZQ53RtFzPsh@mail.python.org> http://hg.python.org/jython/rev/5ce837b1a1d8 changeset: 7065:5ce837b1a1d8 branch: 2.5 parent: 7053:ce225289358a user: Alan Kennedy date: Tue Feb 26 09:21:10 2013 +0000 summary: #2017: jython.bat script pollutes environment! (variables) files: NEWS | 1 + installer/src/java/org/python/util/install/StartScriptGenerator.java | 4 ++++ 2 files changed, 5 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Jython 2.5.4rc2 Bugs Fixed + - [ 2017 ] jython.bat script pollutes environment! (variables) - [ 1899 ] Fix to platform.py to correctly avoid a warning message on Windows. - [ 1988 ] API for threading.condition fails to accept *args for acquire - [ 1753 ] zlib doesn't call end() on compress and decompress diff --git a/installer/src/java/org/python/util/install/StartScriptGenerator.java b/installer/src/java/org/python/util/install/StartScriptGenerator.java --- a/installer/src/java/org/python/util/install/StartScriptGenerator.java +++ b/installer/src/java/org/python/util/install/StartScriptGenerator.java @@ -158,6 +158,10 @@ StringBuilder builder = new StringBuilder(1000); builder.append("@echo off"); builder.append(WIN_CR_LF); + builder.append("rem Prevent leak of environment variables"); + builder.append(WIN_CR_LF); + builder.append("setlocal"); + builder.append(WIN_CR_LF); builder.append("rem This file was generated by the Jython installer"); builder.append(WIN_CR_LF); builder.append("rem Created on {0} by {1}"); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Feb 26 10:25:50 2013 From: jython-checkins at python.org (alan.kennedy) Date: Tue, 26 Feb 2013 10:25:50 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_2=2E5_-=3E_default=29?= =?utf-8?q?=3A_merge_w/2=2E5=3A_=232017=3A_jython=2Ebat_script_pollutes_en?= =?utf-8?q?vironment!_=28variables=29?= Message-ID: <3ZFZQ66PNMzPx1@mail.python.org> http://hg.python.org/jython/rev/67fea7719593 changeset: 7066:67fea7719593 parent: 7064:ae103c4e1aef parent: 7065:5ce837b1a1d8 user: Alan Kennedy date: Tue Feb 26 09:23:35 2013 +0000 summary: merge w/2.5: #2017: jython.bat script pollutes environment! (variables) files: NEWS | 1 + installer/src/java/org/python/util/install/StartScriptGenerator.java | 4 ++++ 2 files changed, 5 insertions(+), 0 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -41,6 +41,7 @@ Jython 2.5.4rc2 Bugs Fixed + - [ 2017 ] jython.bat script pollutes environment! (variables) - [ 1899 ] Fix to platform.py to correctly avoid a warning message on Windows. - [ 1988 ] API for threading.condition fails to accept *args for acquire - [ 1753 ] zlib doesn't call end() on compress and decompress diff --git a/installer/src/java/org/python/util/install/StartScriptGenerator.java b/installer/src/java/org/python/util/install/StartScriptGenerator.java --- a/installer/src/java/org/python/util/install/StartScriptGenerator.java +++ b/installer/src/java/org/python/util/install/StartScriptGenerator.java @@ -158,6 +158,10 @@ StringBuilder builder = new StringBuilder(1000); builder.append("@echo off"); builder.append(WIN_CR_LF); + builder.append("rem Prevent leak of environment variables"); + builder.append(WIN_CR_LF); + builder.append("setlocal"); + builder.append(WIN_CR_LF); builder.append("rem This file was generated by the Jython installer"); builder.append(WIN_CR_LF); builder.append("rem Created on {0} by {1}"); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 27 02:41:06 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 27 Feb 2013 02:41:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Implemented_correct_behavio?= =?utf-8?q?ur_for_str=2Etranslate=28None=2C_deletechars=29?= Message-ID: <3ZG03Q5ZBpzRnl@mail.python.org> http://hg.python.org/jython/rev/48608f3871ea changeset: 7067:48608f3871ea user: Christian Klein date: Tue Feb 26 23:28:47 2013 +0100 summary: Implemented correct behaviour for str.translate(None, deletechars) "For string objects, set the table argument to None for translations that only delete characters" See http://docs.python.org/2/library/stdtypes.html#str.translate files: src/org/python/core/BuiltinDocs.java | 4 ++- src/org/python/core/PyString.java | 21 ++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/org/python/core/BuiltinDocs.java b/src/org/python/core/BuiltinDocs.java --- a/src/org/python/core/BuiltinDocs.java +++ b/src/org/python/core/BuiltinDocs.java @@ -2196,7 +2196,9 @@ "Return a copy of the string S, where all characters occurring\n" + "in the optional argument deletechars are removed, and the\n" + "remaining characters have been mapped through the given\n" + - "translation table, which must be a string of length 256."; + "translation table, which must be a string of length 256.\n" + + "If the table argument is None, no translation is applied and\n" + + "the operation simply removes the characters in deletechars."; public final static String str_upper_doc = "S.upper() -> string\n" + diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java --- a/src/org/python/core/PyString.java +++ b/src/org/python/core/PyString.java @@ -2192,10 +2192,7 @@ @ExposedMethod(defaults = {"null", "null"}, doc = BuiltinDocs.str_translate_doc) final String str_translate(String table, String deletechars) { - if (table == null) { - return getString(); - } - if (table.length() != 256) + if (table != null && table.length() != 256) throw Py.ValueError( "translation table must be 256 characters long"); @@ -2204,12 +2201,16 @@ char c = getString().charAt(i); if (deletechars != null && deletechars.indexOf(c) >= 0) continue; - try { - buf.append(table.charAt(c)); - } - catch (IndexOutOfBoundsException e) { - throw Py.TypeError( - "translate() only works for 8-bit character strings"); + if(table == null) { + buf.append(c); + } else { + try { + buf.append(table.charAt(c)); + } + catch (IndexOutOfBoundsException e) { + throw Py.TypeError( + "translate() only works for 8-bit character strings"); + } } } return buf.toString(); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 27 02:41:08 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 27 Feb 2013 02:41:08 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Unskip_tests_for_str=2Etran?= =?utf-8?q?slate=2C_update_NEWS=2C_ACKNOWLEDGMENTS=2E?= Message-ID: <3ZG03S1F0MzSZL@mail.python.org> http://hg.python.org/jython/rev/599a7c4089d5 changeset: 7068:599a7c4089d5 user: Frank Wierzbicki date: Tue Feb 26 16:43:21 2013 -0800 summary: Unskip tests for str.translate, update NEWS, ACKNOWLEDGMENTS. Thanks Christian Klein! files: ACKNOWLEDGMENTS | 1 + Lib/test/string_tests.py | 8 +++----- NEWS | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -100,6 +100,7 @@ Julian Kennedy Arfrever Frehtes Taifersar Arahesis Andreas St?hrk + Christian Klein Local Variables: mode: indented-text diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -1274,11 +1274,9 @@ self.checkequal('Abc', 'abc', 'translate', table) self.checkequal('xyz', 'xyz', 'translate', table) self.checkequal('yz', 'xyz', 'translate', table, 'x') - #FIXME: - if not test_support.is_jython: - self.checkequal('yx', 'zyzzx', 'translate', None, 'z') - self.checkequal('zyzzx', 'zyzzx', 'translate', None, '') - self.checkequal('zyzzx', 'zyzzx', 'translate', None) + self.checkequal('yx', 'zyzzx', 'translate', None, 'z') + self.checkequal('zyzzx', 'zyzzx', 'translate', None, '') + self.checkequal('zyzzx', 'zyzzx', 'translate', None) self.checkraises(ValueError, 'xyz', 'translate', 'too short', 'strip') self.checkraises(ValueError, 'xyz', 'translate', 'too short') diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7b2 Bugs Fixed + - [ 2020 ] str.translate should delete characters in the second arg when table is None - [ 1753 ] zlib doesn't call end() on compress and decompress Jython 2.7b1 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 27 19:20:07 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 27 Feb 2013 19:20:07 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Modify_test_to_account_for_?= =?utf-8?q?arbitrary_element_return_order=2E?= Message-ID: <3ZGQD71pTwzQ4W@mail.python.org> http://hg.python.org/jython/rev/caac84e8f7f2 changeset: 7069:caac84e8f7f2 user: Jezreel Ng date: Wed Feb 27 10:13:41 2013 -0800 summary: Modify test to account for arbitrary element return order. files: Lib/test/test_collections.py | 7 +++---- 1 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -518,9 +518,7 @@ s &= WithSet('cdef') # This used to fail self.assertEqual(set(s), set('cd')) - @unittest.skipIf(test_support.is_jython, "FIXME: doesn't work in Jython") def test_issue_4920(self): - # MutableSet.pop() method did not work class MySet(collections.MutableSet): __slots__=['__s'] def __init__(self,items=None): @@ -543,8 +541,9 @@ return result def __repr__(self): return "MySet(%s)" % repr(list(self)) - s = MySet([5,43,2,1]) - self.assertEqual(s.pop(), 1) + values = [5,43,2,1] + s = MySet(values) + self.assertIn(s.pop(), values) def test_issue8750(self): empty = WithSet() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Feb 27 19:20:08 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 27 Feb 2013 19:20:08 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Acknowledge_Jezreel_Ng_and_?= =?utf-8?q?update_NEWS=2E?= Message-ID: <3ZGQD84f9LzQ55@mail.python.org> http://hg.python.org/jython/rev/e80a189574d0 changeset: 7070:e80a189574d0 user: Frank Wierzbicki date: Wed Feb 27 10:19:47 2013 -0800 summary: Acknowledge Jezreel Ng and update NEWS. files: ACKNOWLEDGMENTS | 1 + NEWS | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -101,6 +101,7 @@ Arfrever Frehtes Taifersar Arahesis Andreas St?hrk Christian Klein + Jezreel Ng Local Variables: mode: indented-text diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7b2 Bugs Fixed + - [ 1926 ] Adjust MutableSet.pop test so we do not need to skip it - [ 2020 ] str.translate should delete characters in the second arg when table is None - [ 1753 ] zlib doesn't call end() on compress and decompress -- Repository URL: http://hg.python.org/jython