[Jython-checkins] jython (2.5): Move installer into mercurial repository.

frank.wierzbicki jython-checkins at python.org
Sat Feb 9 02:05:44 CET 2013


http://hg.python.org/jython/rev/402cab975983
changeset:   7015:402cab975983
branch:      2.5
parent:      7012:723492dbab02
user:        Oti Humbel <ohumbel at gmail.com>
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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.commons.cli;
+
+/** 
+ * <p>Thrown when more than one option in an option group
+ * has been provided.</p>
+ *
+ * @author John Keyes ( john at integralsource.com )
+ * @see ParseException
+ */
+public class AlreadySelectedException extends ParseException {
+
+    /** 
+     * <p>Construct a new <code>AlreadySelectedException</code> 
+     * with the specified detail message.</p>
+     *
+     * @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
+ * <http://www.apache.org/>.
+ *
+ */
+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 {
+
+    /**
+     * <p>A simple implementation of {@link Parser}'s abstract
+     * {@link Parser#flatten(Options,String[],boolean) flatten} method.</p>
+     *
+     * <p><b>Note:</b> <code>options</code> and <code>stopAtNonOption</code>
+     * are not used in this <code>flatten</code> method.</p>
+     *
+     * @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 <code>arguments</code> 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
+ * <http://www.apache.org/>.
+ *
+ */
+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;
+
+/** 
+ * <p>Represents list of arguments parsed against
+ * a {@link Options} descriptor.<p>
+ *
+ * <p>It allows querying of a boolean {@link #hasOption(String opt)},
+ * in addition to retrieving the {@link #getOptionValue(String opt)}
+ * for options requiring arguments.</p>
+ *
+ * <p>Additionally, any left-over or unrecognized arguments,
+ * are available for further processing.</p>
+ *
+ * @author bob mcwhirter (bob @ werken.com)
+ * @author <a href="mailto:jstrachan at apache.org">James Strachan</a>
+ * @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;
+
+    /**
+     * <p>Creates a command line.</p>
+     */
+    CommandLine() {
+    }
+    
+    /** 
+     * <p>Query to see if an option has been set.</p>
+     *
+     * @param opt Short name of the option
+     * @return true if set, false if not
+     */
+    public boolean hasOption(String opt) {
+        return options.containsKey( opt );
+    }
+
+    /** 
+     * <p>Query to see if an option has been set.</p>
+     *
+     * @param opt character name of the option
+     * @return true if set, false if not
+     */
+    public boolean hasOption( char opt ) {
+        return hasOption( String.valueOf( opt ) );
+    }
+
+    /**
+     * <p>Return the <code>Object</code> type of this <code>Option</code>.</p>
+     *
+     * @param opt the name of the option
+     * @return the type of this <code>Option</code>
+     */
+    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);
+    }
+
+    /**
+     * <p>Return the <code>Object</code> type of this <code>Option</code>.</p>
+     *
+     * @param opt the name of the option
+     * @return the type of opt
+     */
+    public Object getOptionObject( char opt ) {
+        return getOptionObject( String.valueOf( opt ) );
+    }
+
+    /** 
+     * <p>Retrieve the argument, if any, of this option.</p>
+     *
+     * @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];
+    }
+
+    /** 
+     * <p>Retrieve the argument, if any, of this option.</p>
+     *
+     * @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 ) );
+    }
+
+    /** 
+     * <p>Retrieves the array of values, if any, of an option.</p>
+     *
+     * @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[]{});
+    }
+
+    /** 
+     * <p>Retrieves the array of values, if any, of an option.</p>
+     *
+     * @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 ) );
+    }
+    
+    /** 
+     * <p>Retrieve the argument, if any, of an option.</p>
+     *
+     * @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 <code>defaultValue</code>.
+     */
+    public String getOptionValue( String opt, String defaultValue ) {
+        String answer = getOptionValue( opt );
+        return ( answer != null ) ? answer : defaultValue;
+    }
+    
+    /** 
+     * <p>Retrieve the argument, if any, of an option.</p>
+     *
+     * @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 <code>defaultValue</code>.
+     */
+    public String getOptionValue( char opt, String defaultValue ) {
+        return getOptionValue( String.valueOf( opt ), defaultValue );
+    }
+
+    /** 
+     * <p>Retrieve any left-over non-recognized options and arguments</p>
+     *
+     * @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;
+    }
+    
+    /** 
+     * <p>Retrieve any left-over non-recognized options and arguments</p>
+     *
+     * @return remaining items passed in but not parsed as a <code>List</code>.
+     */
+    public List getArgList() {
+        return args;
+    }
+    
+    /** 
+     * jkeyes
+     * - commented out until it is implemented properly
+     * <p>Dump state, suitable for debugging.</p>
+     *
+     * @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();
+    }
+    */
+
+    /**
+     * <p>Add left-over unrecognized option/argument.</p>
+     *
+     * @param arg the unrecognised option/argument.
+     */
+    void addArg(String arg) {
+        args.add( arg );
+    }
+        
+    /**
+     * <p>Add an option to the command line.  The values of 
+     * the option are stored.</p>
+     *
+     * @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 );
+        }
+    }
+
+    /**
+     * <p>Returns an iterator over the Option members of CommandLine.</p>
+     *
+     * @return an <code>Iterator</code> over the processed {@link Option} 
+     * members of this {@link CommandLine}
+     */
+    public Iterator iterator( ) {
+        return hashcodeMap.values().iterator();
+    }
+
+    /**
+     * <p>Returns an array of the processed {@link Option}s.</p>
+     *
+     * @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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.commons.cli;
+
+/**
+ * A class that implements the <code>CommandLineParser</code> 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
+ * <http://www.apache.org/>.
+ *
+ */
+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();
+
+    /**
+     * <p>Resets the members to their original state i.e. remove
+     * all of <code>tokens</code> entries.
+     */
+    private void init() {
+        tokens.clear();
+    }
+
+    /**
+     * <p>This flatten method does so using the following rules:
+     * <ol>
+     *  <li>If an {@link Option} exists for the first character of 
+     *  the <code>arguments</code> entry <b>AND</b> an {@link Option} 
+     *  does not exist for the whole <code>argument</code> 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.</li>
+     *  <li>Otherwise just add the token to the processed tokens list.
+     *  </li>
+     * </ol>
+     * </p>
+     */
+    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
+ * <http://www.apache.org/>.
+ *
+ */
+
+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 );
+      }
+   }
+
+   /**
+    * <p>Prints the usage statement for the specified application.</p>
+    *
+    * @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 <lpad>-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 <code>startPos</code> for the text
+    * in <code>sb</code> with the column width <code>width</code>.
+    * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.cli;
+
+/** 
+ * <p>Thrown when an option requiring an argument
+ * is not provided with an argument.</p>
+ *
+ * @author John Keyes (john at integralsource.com)
+ * @see ParseException
+ */
+public class MissingArgumentException extends ParseException {
+    
+    /** 
+     * <p>Construct a new <code>MissingArgumentException</code> 
+     * with the specified detail message.</p>
+     *
+     * @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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.cli;
+
+/** 
+ * <p>Thrown when a required option has not been provided.</p>
+ *
+ * @author John Keyes ( john at integralsource.com )
+ * @see ParseException
+ */
+public class MissingOptionException extends ParseException {
+    
+    /** 
+     * <p>Construct a new <code>MissingSelectedException</code> 
+     * with the specified detail message.</p>
+     *
+     * @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
+ * <http://www.apache.org/>.
+ *
+ */
+
+/*
+ * 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;
+
+/** <p>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.</p>
+ *
+ * <p>An Option is not created independantly, but is create through
+ * an instance of {@link Options}.<p>
+ *
+ * @see org.apache.commons.cli.Options
+ * @see org.apache.commons.cli.CommandLine
+ *
+ * @author bob mcwhirter (bob @ werken.com)
+ * @author <a href="mailto:jstrachan at apache.org">James Strachan</a>
+ * @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;
+
+    /**
+     * <p>Validates whether <code>opt</code> is a permissable Option
+     * shortOpt.  The rules that specify if the <code>opt</code>
+     * is valid are:</p>
+     * <ul>
+     *  <li><code>opt</code> is not NULL</li>
+     *  <li>a single character <code>opt</code> that is either
+     *  ' '(special case), '?', '@' or a letter</li>
+     *  <li>a multi character <code>opt</code> that only contains
+     *  letters.</li>
+     * </ul>
+     *
+     * @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] + "'" );
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>Returns whether the specified character is a valid Option.</p>
+     *
+     * @param c the option to validate
+     * @return true if <code>c</code> is a letter, ' ', '?' or '@', otherwise false.
+     */
+    private boolean isValidOpt( char c ) {
+        return ( isValidChar( c ) || c == ' ' || c == '?' || c == '@' );
+    }
+
+    /**
+     * <p>Returns whether the specified character is a valid character.</p>
+     *
+     * @param c the character to validate
+     * @return true if <code>c</code> is a letter.
+     */
+    private boolean isValidChar( char c ) {
+        return Character.isJavaIdentifierPart( c );
+    }
+
+    /**
+     * <p>Returns the id of this Option.  This is only set when the
+     * Option shortOpt is a single character.  This is used for switch
+     * statements.</p>
+     *
+     * @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 );
+    }
+    
+    /**
+     * <p>Creates an Option using the specified parameters.</p>
+     *
+     * @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;
+    }
+    
+    /** <p>Retrieve the name of this Option.</p>
+     *
+     * <p>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.<p>
+     *
+     * @return The name of this option
+     */
+    public String getOpt() {
+        return this.opt;
+    }
+
+    /**
+     * <p>Retrieve the type of this Option.</p>
+     * 
+     * @return The type of this option
+     */
+    public Object getType() {
+        return this.type;
+    }
+
+    /**
+     * <p>Sets the type of this Option.</p>
+     *
+     * @param type the type of this Option
+     */
+    public void setType( Object type ) {
+        this.type = type;
+    }
+    
+    /** 
+     * <p>Retrieve the long name of this Option.</p>
+     *
+     * @return Long name of this option, or null, if there is no long name
+     */
+    public String getLongOpt() {
+        return this.longOpt;
+    }
+
+    /**
+     * <p>Sets the long name of this Option.</p>
+     *
+     * @param longOpt the long name of this Option
+     */
+    public void setLongOpt( String longOpt ) {
+        this.longOpt = longOpt;
+    }
+
+    /**
+     * <p>Sets whether this Option can have an optional argument.</p>
+     *
+     * @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;
+    }
+    
+    /** <p>Query to see if this Option has a long name</p>
+     *
+     * @return boolean flag indicating existence of a long name
+     */
+    public boolean hasLongOpt() {
+        return ( this.longOpt != null );
+    }
+    
+    /** <p>Query to see if this Option requires an argument</p>
+     *
+     * @return boolean flag indicating if an argument is required
+     */
+    public boolean hasArg() {
+        return this.numberOfArgs > 0 || numberOfArgs == UNLIMITED_VALUES;
+    }
+    
+    /** <p>Retrieve the self-documenting description of this Option</p>
+     *
+     * @return The string description of this option
+     */
+    public String getDescription() {
+        return this.description;
+    }
+
+     /** 
+      * <p>Query to see if this Option requires an argument</p>
+      *
+      * @return boolean flag indicating if an argument is required
+      */
+     public boolean isRequired() {
+         return this.required;
+     }
+
+     /**
+      * <p>Sets whether this Option is mandatory.</p>
+      *
+      * @param required specifies whether this Option is mandatory
+      */
+     public void setRequired( boolean required ) {
+         this.required = required;
+     }
+
+     /**
+      * <p>Sets the display name for the argument value.</p>
+      *
+      * @param argName the display name for the argument value.
+      */
+     public void setArgName( String argName ) {
+         this.argName = argName;
+     }
+
+     /**
+      * <p>Gets the display name for the argument value.</p>
+      *
+      * @return the display name for the argument value.
+      */
+     public String getArgName() {
+         return this.argName;
+     }
+
+     /**
+      * <p>Returns whether the display name for the argument value
+      * has been set.</p>
+      *
+      * @return if the display name for the argument value has been
+      * set.
+      */
+     public boolean hasArgName() {
+         return (this.argName != null && this.argName.length() > 0 );
+     }
+
+     /** 
+      * <p>Query to see if this Option can take many values</p>
+      *
+      * @return boolean flag indicating if multiple values are allowed
+      */
+     public boolean hasArgs() {
+         return ( this.numberOfArgs > 1 || this.numberOfArgs == UNLIMITED_VALUES );
+     }
+
+     /** 
+      * <p>Sets the number of argument values this Option can take.</p>
+      *
+      * @param num the number of argument values
+      */
+     public void setArgs( int num ) {
+         this.numberOfArgs = num;
+     }
+
+     /**
+      * <p>Sets the value separator.  For example if the argument value
+      * was a Java property, the value separator would be '='.</p>
+      *
+      * @param sep The value separator.
+      */
+     public void setValueSeparator( char sep ) {
+         this.valuesep = sep;
+     }
+
+     /**
+      * <p>Returns the value separator character.</p>
+      *
+      * @return the value separator character.
+      */
+     public char getValueSeparator() {
+         return this.valuesep;
+     }
+
+     /** 
+      * <p>Returns the number of argument values this Option can take.</p>
+      *
+      * @return num the number of argument values
+      */
+     public int getArgs( ) {
+         return this.numberOfArgs;
+     }
+
+    /** 
+     * <p>Dump state, suitable for debugging.</p>
+     *
+     * @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();
+    }
+
+    /**
+     * <p>Adds the specified value to this Option.</p>
+     * 
+     * @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 
+     * <code>null</code> 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 
+     * <code>null</code> 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 
+     * <code>defaultValue</code> 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.cli;
+
+/**
+ * <p>OptionBuilder allows the user to create Options using descriptive
+ * methods.</p>
+ * <p>Details on the Builder pattern can be found at 
+ * <a href="http://c2.com/cgi-bin/wiki?BuilderPattern">http://c2.com/cgi-bin/wiki?BuilderPattern</a>.</p>
+ *
+ * @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() {
+    }
+
+    /**
+     * <p>Resets the member variables to their default values.</p>
+     */
+    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;
+    }
+
+    /**
+     * <p>The next Option created will have the following long option value.</p>
+     *
+     * @param longopt the long option value
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder withLongOpt( String longopt ) {
+        instance.longopt = longopt;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created will require an argument value.</p>
+     *
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder hasArg( ) {
+        instance.numberOfArgs = 1;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created will require an argument value if
+     * <code>hasArg</code> is true.</p>
+     *
+     * @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;
+    }
+
+    /**
+     * <p>The next Option created will have the specified argument value 
+     * name.</p>
+     *
+     * @param name the name for the argument value
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder withArgName( String name ) {
+        instance.argName = name;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created will be required.</p>
+     *
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder isRequired( ) {
+        instance.required = true;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created uses <code>sep</code> as a means to
+     * separate argument values.</p>
+     *
+     * <b>Example:</b>
+     * <pre>
+     * Option opt = OptionBuilder.withValueSeparator( ':' )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * </pre>
+     *
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder withValueSeparator( char sep ) {
+        instance.valuesep = sep;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created uses '<code>=</code>' as a means to
+     * separate argument values.</p>
+     *
+     * <b>Example:</b>
+     * <pre>
+     * Option opt = OptionBuilder.withValueSeparator( )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * </pre>
+     *
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder withValueSeparator( ) {
+        instance.valuesep = '=';
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created will be required if <code>required</code>
+     * is true.</p>
+     *
+     * @param required if true then the Option is required
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder isRequired( boolean required ) {
+        instance.required = required;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created can have unlimited argument values.</p>
+     *
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder hasArgs( ) {
+        instance.numberOfArgs = Option.UNLIMITED_VALUES;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created can have <code>num</code> 
+     * argument values.</p>
+     *
+     * @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;
+    }
+
+    /**
+     * <p>The next Option can have an optional argument.</p>
+     *
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder hasOptionalArg( ) {
+        instance.numberOfArgs = 1;
+        instance.optionalArg = true;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option can have an unlimited number of
+     * optional arguments.</p>
+     *
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder hasOptionalArgs( ) {
+        instance.numberOfArgs = Option.UNLIMITED_VALUES;
+        instance.optionalArg = true;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option can have the specified number of 
+     * optional arguments.</p>
+     *
+     * @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;
+    }
+
+    /**
+     * <p>The next Option created will have a value that will be an instance 
+     * of <code>type</code>.</p>
+     *
+     * @param type the type of the Options argument value
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder withType( Object type ) {
+        instance.type = type;
+        return instance;
+    }
+
+    /**
+     * <p>The next Option created will have the specified description</p>
+     *
+     * @param description a description of the Option's purpose
+     * @return the OptionBuilder instance
+     */
+    public static OptionBuilder withDescription( String description ) {
+        instance.description = description;
+        return instance;
+    }
+
+    /**
+     * <p>Create an Option using the current settings and with 
+     * the specified Option <code>char</code>.</p>
+     *
+     * @param opt the character representation of the Option
+     * @return the Option instance
+     * @throws IllegalArgumentException if <code>opt</code> is not
+     * a valid character.  See Option.
+     */
+    public static Option create( char opt )
+    throws IllegalArgumentException
+    {
+        return create( String.valueOf( opt ) );
+    }
+
+    /**
+     * <p>Create an Option using the current settings</p>
+     *
+     * @return the Option instance
+     * @throws IllegalArgumentException if <code>longOpt</code> has
+     * not been set.  
+     */
+    public static Option create() 
+    throws IllegalArgumentException
+    {
+        if( longopt == null ) {
+            throw new IllegalArgumentException( "must specify longopt" );
+        }
+
+        return create( " " );
+    }
+
+    /**
+     * <p>Create an Option using the current settings and with 
+     * the specified Option <code>char</code>.</p>
+     *
+     * @param opt the <code>java.lang.String</code> representation 
+     * of the Option
+     * @return the Option instance
+     * @throws IllegalArgumentException if <code>opt</code> 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+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 <code>opt</code> 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 
+     * <code>Collection</code>
+     */
+    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 <code>Collection</code>
+     */
+    public Collection getOptions() {
+        return optionList;
+    }
+
+    /**
+     * set the selected option of this group to <code>name</code>.
+     * @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;
+    }
+
+    /**
+     * <p>Returns the stringified version of this OptionGroup.</p>
+     * @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
+ * <http://www.apache.org/>.
+ *
+ */
+
+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;
+
+/** <p>Main entry-point into the library.</p>
+ *
+ * <p>Options represents a collection of {@link Option} objects, which
+ * describe the possible options for a command-line.<p>
+ *
+ * <p>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.<p>
+ *
+ * @see org.apache.commons.cli.CommandLine
+ *
+ * @author bob mcwhirter (bob @ werken.com)
+ * @author <a href="mailto:jstrachan at apache.org">James Strachan</a>
+ * @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: <code>true</code> means in sequence of addition, <code>false</code> will use the
+     * default (undefined) sorting
+     */
+    public void setSortAsAdded(boolean sortAsAdded) {
+        _sortAsAdded = sortAsAdded;
+    }
+
+    /**
+     * <p>Add the specified option group.</p>
+     *
+     * @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;
+    }
+
+    /** <p>Add an option that only contains a short-name</p>
+     * <p>It may be specified as requiring an argument.</p>
+     *
+     * @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;
+    }
+    
+    /** <p>Add an option that contains a short-name and a long-name</p>
+     * <p>It may be specified as requiring an argument.</p>
+     *
+     * @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;
+    }
+
+    /**
+     * <p>Adds an option instance</p>
+     *
+     * @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;
+    }
+    
+    /** <p>Retrieve a read-only list of options in this set</p>
+     *
+     * @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;
+    }
+
+    /**
+     * <p>Returns the Options for use by the HelpFormatter.</p>
+     *
+     * @return the List of Options
+     */
+    List helpOptions() {
+        if (isSortAsAdded()) {
+            return _addedOpts;
+        } else {
+            return new ArrayList(shortOpts.values());
+        }
+    }
+
+    /** <p>Returns the required options as a 
+     * <code>java.util.Collection</code>.</p>
+     *
+     * @return Collection of required options
+     */
+    public List getRequiredOptions() {
+        return requiredOpts;
+    }
+    
+    /** <p>Retrieve the named {@link Option}</p>
+     *
+     * @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();
+    }
+
+    /** 
+     * <p>Returns whether the named {@link Option} is a member
+     * of this {@link Options}</p>
+     *
+     * @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 );
+        }
+    }
+
+    /** <p>Returns the OptionGroup the <code>opt</code>
+     * belongs to.</p>
+     * @param opt the option whose OptionGroup is being queried.
+     *
+     * @return the OptionGroup if <code>opt</code> is part
+     * of an OptionGroup, otherwise return null
+     */
+    public OptionGroup getOptionGroup( Option opt ) {
+        return (OptionGroup)optionGroups.get( opt.getOpt() );
+    }
+    
+    /** <p>Dump state, suitable for debugging.</p>
+     *
+     * @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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.cli;
+
+/** 
+ * <p>Base for Exceptions thrown during parsing of a command-line.</p>
+ *
+ * @author bob mcwhirter (bob @ werken.com)
+ * @version $Revision: 2662 $
+ */
+public class ParseException extends Exception 
+{
+    
+    /** 
+     * <p>Construct a new <code>ParseException</code> 
+     * with the specified detail message.</p>
+     *
+     * @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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.cli;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * <p><code>Parser</code> creates {@link CommandLine}s.</p>
+ *
+ * @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;
+
+    /**
+     * <p>Subclasses must implement this method to reduce
+     * the <code>arguments</code> that have been passed to the parse 
+     * method.</p>
+     *
+     * @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 );
+
+    /**
+     * <p>Parses the specified <code>arguments</code> 
+     * based on the specifed {@link Options}.</p>
+     *
+     * @param options the <code>Options</code>
+     * @param arguments the <code>arguments</code>
+     * @return the <code>CommandLine</code>
+     * @throws ParseException if an error occurs when parsing the
+     * arguments.
+     */
+    public CommandLine parse( Options options, String[] arguments ) 
+    throws ParseException 
+    {
+        return parse( options, arguments, false );
+    }
+
+    /**
+     * <p>Parses the specified <code>arguments</code> 
+     * based on the specifed {@link Options}.</p>
+     *
+     * @param options the <code>Options</code>
+     * @param arguments the <code>arguments</code>
+     * @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 <code>CommandLine</code>
+     * @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;
+    }
+
+    /**
+     * <p>Throws a {@link MissingOptionException} if all of the
+     * required options are no present.</p>
+     */
+    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
+ * <http://www.apache.org/>.
+ *
+ */
+
+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;
+
+    /**
+     * <p>Retrieve the class that <code>ch</code> represents.</p>
+     *
+     * @param ch the specified character
+     * @return The class that <code>ch</code> 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;
+    }
+ 
+    /**
+     * <p>Returns whether <code>ch</code> is a value code, i.e.
+     * whether it represents a class in a pattern.</p>
+     * 
+     * @param ch the specified character
+     * @return true if <code>ch</code> 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;
+    }       
+ 
+    /**
+     * <p>Returns the {@link Options} instance represented by 
+     * <code>pattern</code>.</p>
+     *
+     * @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<sz; i++) {
+            ch = pattern.charAt(i);
+
+            // a value code comes after an option and specifies 
+            // details about it
+            if(!isValueCode(ch)) {
+                if(opt != ' ') {
+                    // we have a previous one to deal with
+                    options.addOption( OptionBuilder.hasArg( type != null )
+                                                    .isRequired( required )
+                                                    .withType( type )
+                                                    .create( opt ) );
+                    required = false;
+                    type = null;
+                    opt = ' ';
+                }
+                opt = ch;
+            } else
+            if(ch == '!') {
+                required = true;
+            } else {
+                type = getValueClass(ch);
+            }
+        }
+
+        if(opt != ' ') {
+            // we have a final one to deal with
+            options.addOption( OptionBuilder.hasArg( type != null )
+                                            .isRequired( required )
+                                            .withType( type )
+                                            .create( opt ) );
+        }
+
+        return options;
+    }
+
+}
diff --git a/installer/src/java/org/apache/commons/cli/PosixParser.java b/installer/src/java/org/apache/commons/cli/PosixParser.java
new file mode 100644
--- /dev/null
+++ b/installer/src/java/org/apache/commons/cli/PosixParser.java
@@ -0,0 +1,342 @@
+/*
+ * $Header$
+ * $Revision: 2833 $
+ * $Date: 2006-06-28 22:51:26 -0700 (Wed, 28 Jun 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
+ * <http://www.apache.org/>.
+ *
+ */
+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;
+
+    /**
+     * <p>Resets the members to their original state i.e. remove
+     * all of <code>tokens</code> entries, set <code>eatTheRest</code>
+     * to false and set <code>currentOption</code> to null.</p>
+     */
+    private void init() {
+        eatTheRest = false;
+        tokens.clear();
+        currentOption = null;
+    }
+
+    /**
+     * <p>An implementation of {@link Parser}'s abstract
+     * {@link Parser#flatten(Options,String[],boolean) flatten} method.</p>
+     *
+     * <p>The following are the rules used by this flatten method.
+     * <ol>
+     *  <li>if <code>stopAtNonOption</code> is <b>true</b> then do not
+     *  burst anymore of <code>arguments</code> entries, just add each
+     *  successive entry without further processing.  Otherwise, ignore
+     *  <code>stopAtNonOption</code>.</li>
+     *  <li>if the current <code>arguments</code> entry is "<b>--</b>"
+     *  just add the entry to the list of processed tokens</li>
+     *  <li>if the current <code>arguments</code> entry is "<b>-</b>"
+     *  just add the entry to the list of processed tokens</li>
+     *  <li>if the current <code>arguments</code> entry is two characters
+     *  in length and the first character is "<b>-</b>" 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 <code>stopAtNonOption</code>
+     *  is true, then the remaining entries are copied to the list of 
+     *  processed tokens.  Otherwise, the current entry is ignored.</li>
+     *  <li>if the current <code>arguments</code> entry is more than two
+     *  characters in length and the first character is "<b>-</b>" 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}.</li>
+     *  <li>if the current <code>arguments</code> entry is not handled 
+     *  by any of the previous rules, then the entry is added to the list
+     *  of processed tokens.</li>
+     * </ol>
+     * </p>
+     *
+     * @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 <code>arguments</code> 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[] {});
+    }
+
+    /**
+     * <p>Adds the remaining tokens to the processed tokens list.</p>
+     *
+     * @param iter An iterator over the remaining tokens
+     */
+    private void gobble( Iterator iter ) {
+        if( eatTheRest ) {
+            while( iter.hasNext() ) {
+                tokens.add( iter.next() );
+            }
+        }
+    }
+
+    /**
+     * <p>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.</p>
+     * <p>If there is a current option and it can have argument
+     * values then add the token to the processed tokens list.</p>
+     * <p>If there is not a current option add the special token
+     * "<b>--</b>" and the current <code>value</code> to the processed
+     * tokens list.  The add all the remaining <code>argument</code>
+     * values to the processed tokens list.</p>
+     *
+     * @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 );
+        }
+    }
+
+    /**
+     * <p>If it is a hyphen then add the hyphen directly to
+     * the processed tokens list.</p>
+     *
+     * @param hyphen The hyphen token
+     */
+    private void processSingleHyphen( String hyphen ) {
+        tokens.add( hyphen );
+    }
+
+    /**
+     * <p>If an {@link Option} exists for <code>token</code> then
+     * set the current option and add the token to the processed 
+     * list.</p>
+     * <p>If an {@link Option} does not exist and <code>stopAtNonOption</code>
+     * is set then ignore the current token and add the remaining tokens
+     * to the processed tokens list directly.</p>
+     *
+     * @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);
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * <p>Breaks <code>token</code> into its constituent parts
+     * using the following algorithm.
+     * <ul>
+     *  <li>ignore the first character ("<b>-</b>" )</li>
+     *  <li>foreach remaining character check if an {@link Option}
+     *  exists with that id.</li>
+     *  <li>if an {@link Option} does exist then add that character
+     *  prepended with "<b>-</b>" to the list of processed tokens.</li>
+     *  <li>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.</li>
+     *  <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> 
+     *  <code>stopAtNonOption</code> <b>IS</b> set then add the special token
+     *  "<b>--</b>" followed by the remaining characters and also 
+     *  the remaining tokens directly to the processed tokens list.</li>
+     *  <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b>
+     *  <code>stopAtNonOption</code> <b>IS NOT</b> set then add that
+     *  character prepended with "<b>-</b>".</li>
+     * </ul>
+     * </p>
+     */
+    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
+ * <http://www.apache.org/>.
+ *
+ */
+
+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 {
+
+    /**
+     * <p>Returns the <code>Object</code> of type <code>obj</code>
+     * with the value of <code>str</code>.</p>
+     *
+     * @param str the command line value
+     * @param obj the type of argument
+     * @return The instance of <code>obj</code> initialised with
+     * the value of <code>str</code>.
+     */
+    public static Object createValue(String str, Object obj) {
+        return createValue(str, (Class)obj);
+    }
+
+    /**
+     * <p>Returns the <code>Object</code> of type <code>clazz</code>
+     * with the value of <code>str</code>.</p>
+     *
+     * @param str the command line value
+     * @param clazz the type of argument
+     * @return The instance of <code>clazz</code> initialised with
+     * the value of <code>str</code>.
+     */
+    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;
+        }
+    }
+
+    /**
+      * <p>Create an Object from the classname and empty constructor.</p>
+      *
+      * @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;
+    }
+
+    /**
+     * <p>Create a number from a String.</p>
+     *
+     * @param str the value
+     * @return the number represented by <code>str</code>, if <code>str</code>
+     * 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;
+        }
+    }
+
+    /**
+     * <p>Returns the class whose name is <code>str</code>.</p>
+     *
+     * @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;
+        }
+    }
+
+    /**
+     * <p>Returns the date represented by <code>str</code>.</p>
+     *
+     * @param str the date string
+     * @return The date if <code>str</code> 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;
+    }
+
+    /**
+     * <p>Returns the URL represented by <code>str</code>.</p>
+     *
+     * @param str the URL string
+     * @return The URL is <code>str</code> 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;
+        }
+    }
+
+    /**
+     * <p>Returns the File represented by <code>str</code>.</p>
+     *
+     * @param str the File location
+     * @return The file represented by <code>str</code>.
+     */
+    public static File createFile(String str) {
+        return new File(str);
+    }
+
+    /**
+     * <p>Returns the File[] represented by <code>str</code>.</p>
+     *
+     * @param str the paths to the files
+     * @return The File[] represented by <code>str</code>.
+     */
+    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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.cli;
+
+/** 
+ * <p>Exception thrown during parsing signalling an unrecognized
+ * option was seen.<p>
+ *
+ * @author bob mcwhiter (bob @ werken.com)
+ * @version $Revision: 2662 $
+ */
+public class UnrecognizedOptionException extends ParseException {
+    
+    /** 
+     * <p>Construct a new <code>UnrecognizedArgumentException</code> 
+     * with the specified detail message.</p>
+     *
+     * @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 <i>cancel </i> button is visible
+     */
+    protected abstract boolean isCancelVisible();
+
+    /**
+     * @return whether the <i>next </i> button is visible
+     */
+    protected abstract boolean isNextVisible();
+
+    /**
+     * @return whether the <i>previous </i> 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 <i>next </i> 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.
+ * <p>
+ * Features are:
+ * <ul>
+ * <li>wait for the child process to finish.
+ * <li>kill the child process after a specified timeout.
+ * <li>get the output of the child process (System.out and System.err) redirected to the calling
+ * process, unless in silent mode.
+ * </ul>
+ */
+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: <code>INFINITE_TIMEOUT</code> 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: <code>INFINITE_TIMEOUT</code> 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.
+     * <p>
+     * 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.
+     * <p>
+     * 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<String> 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<String> answers,
+                            boolean answerRequired,
+                            String defaultAnswer) {
+        try {
+            if (answers != null && answers.size() > 0) {
+                question = question + " " + _BEGIN_ANSWERS;
+                Iterator<String> 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<String> 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.
+     * 
+     * <pre>
+     *            (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *            (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * </pre>
+     */
+    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<String> availableLanguages = new ArrayList<String>(2);
+        availableLanguages.add(getText(C_ENGLISH));
+        availableLanguages.add(getText(C_GERMAN)); // 1 == German
+        List<String> answers = new ArrayList<String>(availableLanguages.size());
+        String languages = "";
+        String defaultAnswer = null;
+        for (Iterator<String> 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<String> getTypeAnswers() {
+        List<String> answers = new ArrayList<String>(4);
+        answers.add(Installation.ALL);
+        answers.add(Installation.STANDARD);
+        answers.add(Installation.MINIMUM);
+        answers.add(Installation.STANDALONE);
+        return answers;
+    }
+
+    private List<String> getYNAnswers() {
+        List<String> answers = new ArrayList<String>(2);
+        answers.add(getText(C_YES));
+        answers.add(getText(C_NO));
+        return answers;
+    }
+
+    private List<String> getInExcludeAnswers() {
+        List<String> answers = new ArrayList<String>(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 <code>true</code> 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 <code>true</code> if successful, <code>false</code> 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
+     * <p>
+     * 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.
+     * <p>
+     * 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<br>
+     * 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<br>
+     * 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<br>
+     * 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 <code>false</code> if newResult or existingResult are false, <code>true</code>
+     *         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.<br>
+     *            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 <code>(args, null, null)</code>, see <code>main(args)</code>.
+     * <p>
+     * However, in autotesting mode (<code>commandLine.hasAutotestOption()</code>), we pass in Autotest and Tunnel,
+     * see <code>driverMain(args, autotest, tunnel)</code>.
+     * <p>
+     * This means that in autotesting mode this method will call itself (via <code>InstallationDriver.drive()</code>),
+     * 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<String> names = (Enumeration<String>)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 <code>true</code> 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 <code>true</code> 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 <code>true</code> if all arguments are valid, <code>false</code> otherwise. No help is printed if
+     * <code>false</code> 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, <code>null</code> 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 = "<unknown>";
+        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<String> getExcludeDirs() throws IOException {
+        List<String> excludeDirs = new ArrayList<String>();
+        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<JarEntry> 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 <code>charsetName</code>.
+     * 
+     * @param jarFile
+     * @param entry
+     * @param charsetName the name of the Charset
+     * 
+     * @return the contents of the text file as String (if reading was successful), <code>null</code> otherwise.<br>
+     * 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. <br>
+ * 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<InstallationListener> _installationListeners;
+
+    public JarInstaller(ProgressListener progressListener, JarInfo jarInfo) {
+        _progressListener = progressListener;
+        _jarInfo = jarInfo;
+        _installationListeners = new ArrayList<InstallationListener>();
+    }
+
+    /**
+     * Do the pysical installation:
+     * <ul>
+     * <li>unzip the files
+     * <li>generate the start scripts
+     * </ul>
+     * 
+     * @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<String> excludeDirs = _jarInfo.getExcludeDirs();
+            List<String> coreLibFiles = new ArrayList<String>();
+            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<String> 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<InstallationListener> 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<String> getCoreLibFiles() {
+        List<String> coreLibFiles = new ArrayList<String>();
+        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<String> 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<String> 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
+ * <p>
+ * Note that the system property <code>java.home</code> 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<String, String> _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
+     * <p>
+     * Note: if the current java home is not valid, {@link JavaHomeHandler#getExecutableName()}
+     * still returns the name of a callable java
+     * 
+     * @return <code>true</code> if we have a valid java home, <code>false</code> 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 <code>true</code> if the current java home is a deviation, <code>false</code>
+     *         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<String, String> 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<String, String> getExecutableNames() {
+        if (_executableNames == null) {
+            _executableNames = new HashMap<String, String>();
+        }
+        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 = "<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.
+     * <p>
+     * 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 (<code>null</code> = 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 (<code>null</code> =
+     * 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 <code>protected</code> 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 <br>
+     * {1} : user.name <br>
+     * {2} : target directory <br>
+     */
+    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 <code>isDeviation()</code>.
+     */
+    protected JavaHomeHandler getJavaHomeHandler() {
+        return _javaHomeHandler;
+    }
+
+    /**
+     * @return <code>true</code> 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
+     * <p>
+     * This adds (if present):
+     * <ul>
+     * <li> target directory (should always be present)
+     * <li> verbose
+     * <li> jre
+     * </ul>
+     */
+    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 <code>_name</code>.
+     */
+    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
+     * <p>
+     * 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).
+     * <p>
+     * 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 '!'...
+     * <p>
+     * Currently there is no way on windows, because the enabledelayedexpansion in jython.bat cannot
+     * handle exclamation marks in variable names.
+     * 
+     * @return <code>true</code> if we can handle exclamation marks, <code>false</code> 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.
+ * 
+ * <pre>
+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * </pre>
+ */
+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<SilentAutotest> silentTests = new ArrayList<SilentAutotest>(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<SilentAutotest> 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<ConsoleAutotest> consoleTests = new ArrayList<ConsoleAutotest>(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<ConsoleAutotest> 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<GuiAutotest> guiTests = new ArrayList<GuiAutotest>(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<GuiAutotest> 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 <code>true</code> 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<JarEntry> 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.
+ * 
+ * <pre>
+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * </pre>
+ */
+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.
+ * <p>
+ * 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<AllTests> 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<File> 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<File> classFiles = new ArrayList<File>(potentialDirectories.size());
+        Collections.sort(potentialDirectories, new FileComparator());
+        Iterator<File> 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<File> 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<File> {
+        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;
+
+/**
+ * <p>
+ * This is a collection of tests that test real world
+ * applications command lines.
+ * </p>
+ * 
+ * <p>
+ * The following are the applications that are tested:
+ * <ul>
+ * <li>Ant</li>
+ * </ul>
+ * </p>
+ *
+ * @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<String, String> mandatoryEntries = new HashMap<String, String>(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<JarEntry> 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<String> fileNamesSet = new HashSet<String>(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<String> fileNamesSet = new HashSet<String>(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<String> fileNamesSet = new HashSet<String>(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<String>(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 <code>System.out</code>)
+     */
+    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.
+     * 
+     * <pre>
+     *     (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *     (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * </pre>
+     */
+    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


More information about the Jython-checkins mailing list