[izpack-changes] r1695 - in izpack-src/trunk: . src src/lib/com/izforge/izpack src/lib/com/izforge/izpack/compiler src/lib/com/izforge/izpack/installer src/lib/com/izforge/izpack/io src/lib/com/izforge/izpack/panels

noreply at berlios.de noreply at berlios.de
Tue Jan 9 12:35:58 CET 2007


Author: dreil
Date: 2007-01-09 12:35:48 +0100 (Tue, 09 Jan 2007)
New Revision: 1695

Added:
   izpack-src/trunk/src/lib/com/izforge/izpack/XPackFile.java
   izpack-src/trunk/src/lib/com/izforge/izpack/compiler/MultiVolumePackager.java
   izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeInstaller.java
   izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeUnpacker.java
   izpack-src/trunk/src/lib/com/izforge/izpack/io/
   izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningInputStream.java
   izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningOutputStream.java
   izpack-src/trunk/src/lib/com/izforge/izpack/io/VolumeNotFoundException.java
   izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaDialog.java
   izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaFileFilter.java
Modified:
   izpack-src/trunk/Versions.txt
   izpack-src/trunk/src/build.xml
Log:
Added support for splitting the installer on many disks


Modified: izpack-src/trunk/Versions.txt
===================================================================
--- izpack-src/trunk/Versions.txt	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/Versions.txt	2007-01-09 11:35:48 UTC (rev 1695)
@@ -44,6 +44,7 @@
   (Vladimir Ralev, JBoss/RedHat, via Julien Ponge)
 - PathInputPanel: logged a stacktrace on Linux systems when no defaultInstallDir was specified and
   no TargetPanel.dir.x resource was present (Stefan Wachter via Julien Ponge)
+- Added MultiVolumePackager, MultiVolumeUnpacker, MultiVolumeInstaller to support splitting the installer (Dennis Reil)
 
   > 3.9.0 (build 2006.09.25)
 - Fix NullPointerException in CompilerConfig, if you specify a 

Modified: izpack-src/trunk/src/build.xml
===================================================================
--- izpack-src/trunk/src/build.xml	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/build.xml	2007-01-09 11:35:48 UTC (rev 1695)
@@ -274,6 +274,7 @@
                debug="${debug}"
                debuglevel="${debuglevel}">
             <include name="com/izforge/izpack/*.java" />
+            <include name="com/izforge/izpack/io/*.java"/>
             <include name="com/izforge/izpack/compiler/*.java" />
             <include name="com/izforge/izpack/compressor/*.java" />
             <include name="com/izforge/izpack/ant/*.java" />
@@ -297,6 +298,7 @@
             </manifest>
             <fileset dir="${build.dir}">
                 <include name="com/izforge/izpack/*.class" />
+                <include name="com/izforge/izpack/io/*.class"/>
                 <include name="com/izforge/izpack/compiler/*.class" />
                 <include name="com/izforge/izpack/compressor/*.class" />
                 <include name="com/izforge/izpack/util/OsConstraint.class" />
@@ -366,10 +368,13 @@
             <include name="com/izforge/izpack/panels/PathSelectionPanel.java" />
             <include name="com/izforge/izpack/*.java" />
             <include name="com/izforge/izpack/gui/*.java" />
+            <include name="com/izforge/izpack/io/*.java" />          
             <include name="com/izforge/izpack/installer/*.java" />
+            <include name="com/izforge/izpack/panels/NextMedia*.java"/>
             <include name="com/izforge/izpack/util/*.java" />
             <include name="com/izforge/izpack/util/os/*.java" />
             <include name="com/izforge/izpack/rules/*.java" />
+            <include name="com/izforge/izpack/uninstaller/SelfModifier*.java"/>
             <include name="net/n3/nanoxml/*.java" />
         </javac>
     </target>
@@ -386,10 +391,13 @@
                 <include name="com/izforge/izpack/panels/PathSelectionPanel.class" />
                 <include name="com/izforge/izpack/*.class" />
                 <include name="com/izforge/izpack/gui/*.class" />
+                <include name="com/izforge/izpack/io/*.class"/>
                 <include name="com/izforge/izpack/installer/*.class" />
                 <include name="com/izforge/izpack/util/*.class" />
                 <include name="com/izforge/izpack/util/**/*.class" />
+                <include name="com/izforge/izpack/panels/NextMedia*.class"/>
                 <include name="com/izforge/izpack/rules/*.class" />
+                <include name="com/izforge/izpack/uninstaller/SelfModifier*.class"/>
                 <include name="net/n3/nanoxml/*.class" />
             </fileset>
             <zipfileset src="${basedir}/lib/jakarta-regexp-1.3.jar">

Added: izpack-src/trunk/src/lib/com/izforge/izpack/XPackFile.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/XPackFile.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/XPackFile.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,99 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+import java.util.Map;
+
+import com.izforge.izpack.PackFile;
+
+/**
+ * Extends the packfile by the information at which file position an entry is stored
+ * 
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ * 
+ */
+public class XPackFile extends PackFile
+{
+
+    private static final long serialVersionUID = 5875050264763504283L;
+
+    protected long archivefileposition;
+
+    protected PackFile packfile;
+
+    /**
+     * @param src
+     * @param target
+     * @param osList
+     * @param override
+     * @throws FileNotFoundException
+     */
+    public XPackFile(File src, String target, List osList, int override)
+            throws FileNotFoundException
+    {
+        super(src, target, osList, override);
+        this.archivefileposition = 0;
+    }
+
+    /**
+     * @param src
+     * @param target
+     * @param osList
+     * @param override
+     * @param additionals
+     * @throws FileNotFoundException
+     */
+    public XPackFile(File src, String target, List osList, int override, Map additionals)
+            throws FileNotFoundException
+    {
+        super(src, target, osList, override, additionals);
+        this.archivefileposition = 0;
+    }
+
+    public XPackFile(PackFile packf) throws FileNotFoundException
+    {
+        super(new File(packf.sourcePath), packf.getTargetPath(), packf.osConstraints(), packf
+                .override(), packf.getAdditionals());
+        this.archivefileposition = 0;
+        this.packfile = packf;
+    }
+
+    public long getArchivefileposition()
+    {
+        return archivefileposition;
+    }
+
+    public void setArchivefileposition(long archivefileposition)
+    {
+        this.archivefileposition = archivefileposition;
+    }
+
+    public PackFile getPackfile()
+    {
+        return packfile;
+    }
+
+    public void setPackfile(PackFile packfile)
+    {
+        this.packfile = packfile;
+    }
+
+}
\ No newline at end of file

Added: izpack-src/trunk/src/lib/com/izforge/izpack/compiler/MultiVolumePackager.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/compiler/MultiVolumePackager.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/compiler/MultiVolumePackager.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,938 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.compiler;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import net.n3.nanoxml.XMLElement;
+
+import com.izforge.izpack.CustomData;
+import com.izforge.izpack.GUIPrefs;
+import com.izforge.izpack.Info;
+import com.izforge.izpack.Pack;
+import com.izforge.izpack.PackFile;
+import com.izforge.izpack.Panel;
+import com.izforge.izpack.XPackFile;
+import com.izforge.izpack.compressor.PackCompressor;
+import com.izforge.izpack.compressor.PackCompressorFactory;
+import com.izforge.izpack.io.FileSpanningInputStream;
+import com.izforge.izpack.io.FileSpanningOutputStream;
+import com.izforge.izpack.util.Debug;
+
+/**
+ * The packager class. The packager is used by the compiler to put files into an installer, and
+ * create the actual installer files.
+ * 
+ * This is a packager, which packs everything into multi volumes.
+ * 
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ */
+public class MultiVolumePackager implements IPackager
+{
+
+    public static final String INSTALLER_PAK_NAME = "installer";
+
+    /** Path to the skeleton installer. */
+    public static final String SKELETON_SUBPATH = "lib/installer.jar";
+
+    /** Base file name of all jar files. This has no ".jar" suffix. */
+    private File baseFile = null;
+
+    /** Executable zipped output stream. First to open, last to close. */
+    private ZipOutputStream primaryJarStream;
+
+    /** Basic installer info. */
+    private Info info = null;
+
+    /** Gui preferences of instatller. */
+    private GUIPrefs guiPrefs = null;
+
+    /** The variables used in the project */
+    private Properties variables = new Properties();
+
+    /** The ordered panels informations. */
+    private List panelList = new ArrayList();
+
+    /** The ordered packs informations (as PackInfo objects). */
+    private List packsList = new ArrayList();
+
+    /** The ordered langpack ISO3 names. */
+    private List langpackNameList = new ArrayList();
+
+    /** The ordered custom actions informations. */
+    private List customDataList = new ArrayList();
+
+    /** The langpack URLs keyed by ISO3 name. */
+    private Map installerResourceURLMap = new HashMap();
+
+    /** Jar file URLs who's contents will be copied into the installer. */
+    private Set includedJarURLs = new HashSet();
+
+    /** Each pack is created in a separte jar if webDirURL is non-null. */
+    private boolean packJarsSeparate = false;
+
+    /** The listeners. */
+    private PackagerListener listener;
+
+    /** The compression format to be used for pack compression */
+    private PackCompressor compressor;
+
+    /** Files which are always written into the container file */
+    private HashMap alreadyWrittenFiles = new HashMap();
+    
+    private XMLElement configdata = null;
+
+    /**
+     * The constructor.
+     * 
+     * @throws CompilerException
+     */
+    public MultiVolumePackager() throws CompilerException
+    {
+        this("default");
+    }
+
+    /**
+     * Extended constructor.
+     * 
+     * @param compr_format Compression format to be used for packs compression format (if supported)
+     * @throws CompilerException
+     */
+    public MultiVolumePackager(String compr_format) throws CompilerException
+    {
+        this(compr_format, -1);
+    }
+
+    /**
+     * Extended constructor.
+     * 
+     * @param compr_format Compression format to be used for packs
+     * @param compr_level Compression level to be used with the chosen compression format (if
+     * supported)
+     * @throws CompilerException
+     */
+    public MultiVolumePackager(String compr_format, int compr_level) throws CompilerException
+    {
+        setCompressorOptions(compr_format, compr_level);
+    }
+
+    /**
+     * Create the installer, beginning with the specified jar. If the name specified does not end in
+     * ".jar", it is appended. If secondary jars are created for packs (if the Info object added has
+     * a webDirURL set), they are created in the same directory, named sequentially by inserting
+     * ".pack#" (where '#' is the pack number) ".jar" suffix: e.g. "foo.pack1.jar". If any file
+     * exists, it is overwritten.
+     */
+    public void createInstaller(File primaryFile) throws Exception
+    {
+        // first analyze the configuration
+        this.analyzeConfigurationInformation();
+        
+        // preliminary work
+        String baseName = primaryFile.getName();
+        if (baseName.endsWith(".jar"))
+        {
+            baseName = baseName.substring(0, baseName.length() - 4);
+            baseFile = new File(primaryFile.getParentFile(), baseName);
+        }
+        else
+            baseFile = primaryFile;
+
+        info.setInstallerBase(baseFile.getName());
+        packJarsSeparate = (info.getWebDirURL() != null);
+
+        // primary (possibly only) jar. -1 indicates primary
+        primaryJarStream = getJarOutputStream(baseFile.getName() + ".jar");
+
+        sendStart();
+
+        // write the primary jar. MUST be first so manifest is not overwritten
+        // by
+        // an included jar
+        System.out.println("Writing skeleton installer.");
+        writeSkeletonInstaller();
+        writeInstallerObject("info", info);
+        writeInstallerObject("vars", variables);
+        writeInstallerObject("GUIPrefs", guiPrefs);
+        writeInstallerObject("panelsOrder", panelList);
+        writeInstallerObject("customData", customDataList);
+        writeInstallerObject("langpacks.info", langpackNameList);
+        writeInstallerResources();
+        writeIncludedJars();
+
+        // Pack File Data may be written to separate jars
+        String packfile = baseFile.getParent() + File.separator + INSTALLER_PAK_NAME;
+        writePacks(new File(packfile));
+
+        // Finish up. closeAlways is a hack for pack compressions other than
+        // default. Some of it (e.g. BZip2) closes the slave of it also.
+        // But this should not be because the jar stream should be open
+        // for the next pack. Therefore an own JarOutputStream will be used
+        // which close method will be blocked.
+        // primaryJarStream.closeAlways();
+        primaryJarStream.close();
+
+        sendStop();
+    }
+
+    /***********************************************************************************************
+     * Listener assistance
+     **********************************************************************************************/
+
+    private void analyzeConfigurationInformation()
+    {
+        String classname = this.getClass().getName();
+        String sizeprop = classname + ".volumesize";
+        String freespaceprop = classname + ".firstvolumefreespace";        
+        if (this.configdata == null){
+            // no configdata given, set default values
+            this.variables.setProperty(sizeprop, Long.toString(FileSpanningOutputStream.DEFAULT_VOLUME_SIZE));
+            this.variables.setProperty(freespaceprop, Long.toString(FileSpanningOutputStream.DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE));            
+        }
+        else {            
+            // configdata was set
+            String volumesize = configdata.getAttribute("volumesize", Long.toString(FileSpanningOutputStream.DEFAULT_VOLUME_SIZE));
+            String freespace = configdata.getAttribute("firstvolumefreespace", Long.toString(FileSpanningOutputStream.DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE));
+            this.variables.setProperty(sizeprop, volumesize);
+            this.variables.setProperty(freespaceprop, freespace);
+        }         
+    }
+
+    /**
+     * Get the PackagerListener.
+     * 
+     * @return the current PackagerListener
+     */
+    public PackagerListener getPackagerListener()
+    {
+        return listener;
+    }
+
+    /**
+     * Adds a listener.
+     * 
+     * @param listener The listener.
+     */
+    public void setPackagerListener(PackagerListener listener)
+    {
+        this.listener = listener;
+    }
+
+    /**
+     * Dispatches a message to the listeners.
+     * 
+     * @param job The job description.
+     */
+    private void sendMsg(String job)
+    {
+        sendMsg(job, PackagerListener.MSG_INFO);
+    }
+
+    /**
+     * Dispatches a message to the listeners at specified priority.
+     * 
+     * @param job The job description.
+     * @param priority The message priority.
+     */
+    private void sendMsg(String job, int priority)
+    {
+        Debug.trace(job);
+        if (listener != null) listener.packagerMsg(job, priority);
+    }
+
+    /** Dispatches a start event to the listeners. */
+    private void sendStart()
+    {
+        if (listener != null) listener.packagerStart();
+    }
+
+    /** Dispatches a stop event to the listeners. */
+    private void sendStop()
+    {
+        if (listener != null) listener.packagerStop();
+    }
+
+    /***********************************************************************************************
+     * Public methods to add data to the Installer being packed
+     **********************************************************************************************/
+
+    /**
+     * Sets the informations related to this installation.
+     * 
+     * @param info The info section.
+     * @exception Exception Description of the Exception
+     */
+    public void setInfo(Info info) throws Exception
+    {
+        sendMsg("Setting the installer information", PackagerListener.MSG_VERBOSE);
+        this.info = info;
+        if (!getCompressor().useStandardCompression()
+                && getCompressor().getDecoderMapperName() != null)
+        {
+            this.info.setPackDecoderClassName(getCompressor().getDecoderMapperName());
+        }
+    }
+
+    /**
+     * Sets the GUI preferences.
+     * 
+     * @param prefs The new gUIPrefs value
+     */
+    public void setGUIPrefs(GUIPrefs prefs)
+    {
+        sendMsg("Setting the GUI preferences", PackagerListener.MSG_VERBOSE);
+        guiPrefs = prefs;
+    }
+
+    /**
+     * Allows access to add, remove and update the variables for the project, which are maintained
+     * in the packager.
+     * 
+     * @return map of variable names to values
+     */
+    public Properties getVariables()
+    {
+        return variables;
+    }
+
+    /**
+     * Add a panel, where order is important. Only one copy of the class files neeed are inserted in
+     * the installer.
+     */
+    public void addPanelJar(Panel panel, URL jarURL)
+    {
+        panelList.add(panel); // serialized to keep order/variables correct
+        addJarContent(jarURL); // each included once, no matter how many times
+        // added
+    }
+
+    /**
+     * Add a custom data like custom actions, where order is important. Only one copy of the class
+     * files neeed are inserted in the installer.
+     * 
+     * @param ca custom action object
+     * @param url the URL to include once
+     */
+    public void addCustomJar(CustomData ca, URL url)
+    {
+        customDataList.add(ca); // serialized to keep order/variables correct
+        addJarContent(url); // each included once, no matter how many times
+        // added
+    }
+
+    /**
+     * Adds a pack, order is mostly irrelevant.
+     * 
+     * @param pack contains all the files and items that go with a pack
+     */
+    public void addPack(PackInfo pack)
+    {
+        packsList.add(pack);
+    }
+
+    /**
+     * Gets the packages list
+     */
+    public List getPacksList()
+    {
+        return packsList;
+    }
+
+    /**
+     * Adds a language pack.
+     * 
+     * @param iso3 The ISO3 code.
+     * @param xmlURL The location of the xml local info
+     * @param flagURL The location of the flag image resource
+     */
+    public void addLangPack(String iso3, URL xmlURL, URL flagURL)
+    {
+        sendMsg("Adding langpack: " + iso3, PackagerListener.MSG_VERBOSE);
+        // put data & flag as entries in installer, and keep array of iso3's
+        // names
+        langpackNameList.add(iso3);
+        addResource("flag." + iso3, flagURL);
+        installerResourceURLMap.put("langpacks/" + iso3 + ".xml", xmlURL);
+    }
+
+    /**
+     * Adds a resource.
+     * 
+     * @param resId The resource Id.
+     * @param url The location of the data
+     */
+    public void addResource(String resId, URL url)
+    {
+        sendMsg("Adding resource: " + resId, PackagerListener.MSG_VERBOSE);
+        installerResourceURLMap.put("res/" + resId, url);
+    }
+
+    /**
+     * Adds a native library.
+     * 
+     * @param name The native library name.
+     * @param url The url to get the data from.
+     * @exception Exception Description of the Exception
+     */
+    public void addNativeLibrary(String name, URL url) throws Exception
+    {
+        sendMsg("Adding native library: " + name, PackagerListener.MSG_VERBOSE);
+        installerResourceURLMap.put("native/" + name, url);
+    }
+
+    /**
+     * Adds a jar file content to the installer. Package structure is maintained. Need mechanism to
+     * copy over signed entry information.
+     * 
+     * @param jarURL The url of the jar to add to the installer. We use a URL so the jar may be
+     * nested within another.
+     */
+    public void addJarContent(URL jarURL)
+    {
+        addJarContent(jarURL, null);
+    }
+
+    /**
+     * Adds a jar file content to the installer. Package structure is maintained. Need mechanism to
+     * copy over signed entry information.
+     * 
+     * @param jarURL The url of the jar to add to the installer. We use a URL so the jar may be
+     * nested within another.
+     */
+    public void addJarContent(URL jarURL, List files)
+    {
+        Object[] cont = { jarURL, files};
+        sendMsg("Adding content of jar: " + jarURL.getFile(), PackagerListener.MSG_VERBOSE);
+        includedJarURLs.add(cont);
+    }
+
+    /**
+     * Marks a native library to be added to the uninstaller.
+     * 
+     * @param data the describing custom action data object
+     */
+    public void addNativeUninstallerLibrary(CustomData data)
+    {
+        customDataList.add(data); // serialized to keep order/variables
+        // correct
+
+    }
+
+    /***********************************************************************************************
+     * Private methods used when writing out the installer to jar files.
+     **********************************************************************************************/
+
+    /**
+     * Write skeleton installer to primary jar. It is just an included jar, except that we copy the
+     * META-INF as well.
+     */
+    private void writeSkeletonInstaller() throws IOException
+    {
+        sendMsg("Copying the skeleton installer", PackagerListener.MSG_VERBOSE);
+        
+        
+        InputStream is = MultiVolumePackager.class.getResourceAsStream("/" + SKELETON_SUBPATH);
+        if (is == null)
+        {
+            File skeleton = new File(Compiler.IZPACK_HOME, SKELETON_SUBPATH);
+            is = new FileInputStream(skeleton);
+        }
+        ZipInputStream inJarStream = new ZipInputStream(is);
+        
+        // copy anything except the manifest.mf
+        List excludes = new ArrayList();
+        excludes.add("META-INF.MANIFEST.MF");
+        copyZipWithoutExcludes(inJarStream, primaryJarStream,excludes);
+
+        // ugly code to modify the manifest-file to set MultiVolumeInstaller as main class
+        // reopen Stream
+        is = MultiVolumePackager.class.getResourceAsStream("/" + SKELETON_SUBPATH);
+        if (is == null)
+        {
+            File skeleton = new File(Compiler.IZPACK_HOME, SKELETON_SUBPATH);
+            is = new FileInputStream(skeleton);
+        }
+        inJarStream = new ZipInputStream(is);                
+        boolean found = false;
+        ZipEntry ze = null;
+        String modifiedmanifest = null;
+        while (((ze = inJarStream.getNextEntry()) != null) && !found){            
+            if ("META-INF/MANIFEST.MF".equals(ze.getName())){
+                long size = ze.getSize();
+                byte[] buffer = new byte[4096];
+                int readbytes = 0;
+                int totalreadbytes = 0;
+                StringBuffer manifest = new StringBuffer();
+                while (((readbytes = inJarStream.read(buffer)) > 0) && (totalreadbytes < size)){
+                    totalreadbytes += readbytes;
+                    String tmp = new String(buffer,0,readbytes,"utf-8");
+                    manifest.append(tmp);
+                }
+                
+                
+                StringReader stringreader = new StringReader(manifest.toString());
+                BufferedReader reader = new BufferedReader(stringreader);
+                String line = null;
+                StringBuffer modified = new StringBuffer();
+                while ((line = reader.readLine()) != null){
+                    if (line.startsWith("Main-Class:")){
+                        line = "Main-Class: com.izforge.izpack.installer.MultiVolumeInstaller";
+                    }
+                    modified.append(line);
+                    modified.append("\r\n");
+                }                
+                reader.close();
+                modifiedmanifest = modified.toString();
+                /*
+                System.out.println("Manifest:");
+                System.out.println(manifest.toString());
+                System.out.println("Modified Manifest:");
+                System.out.println(modified.toString());
+                */
+                break;
+            }
+        }
+        
+        primaryJarStream.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));        
+        primaryJarStream.write(modifiedmanifest.getBytes());
+        primaryJarStream.closeEntry();
+    }
+
+    /**
+     * Write an arbitrary object to primary jar.
+     */
+    private void writeInstallerObject(String entryName, Object object) throws IOException
+    {
+        primaryJarStream.putNextEntry(new ZipEntry(entryName));
+        ObjectOutputStream out = new ObjectOutputStream(primaryJarStream);
+        out.writeObject(object);
+        out.flush();
+        primaryJarStream.closeEntry();
+    }
+
+    /** Write the data referenced by URL to primary jar. */
+    private void writeInstallerResources() throws IOException
+    {
+        sendMsg("Copying " + installerResourceURLMap.size() + " files into installer");
+
+        Iterator i = installerResourceURLMap.keySet().iterator();
+        while (i.hasNext())
+        {
+            String name = (String) i.next();
+            InputStream in = ((URL) installerResourceURLMap.get(name)).openStream();
+            primaryJarStream.putNextEntry(new ZipEntry(name));
+            copyStream(in, primaryJarStream);
+            primaryJarStream.closeEntry();
+            in.close();
+        }
+    }
+
+    /** Copy included jars to primary jar. */
+    private void writeIncludedJars() throws IOException
+    {
+        sendMsg("Merging " + includedJarURLs.size() + " jars into installer");
+
+        Iterator i = includedJarURLs.iterator();
+        while (i.hasNext())
+        {
+            Object[] current = (Object[]) i.next();
+            InputStream is = ((URL) current[0]).openStream();
+            ZipInputStream inJarStream = new ZipInputStream(is);
+            copyZip(inJarStream, primaryJarStream, (List) current[1]);
+        }
+    }
+
+    /**
+     * Write Packs to primary jar or each to a separate jar.
+     */
+    private void writePacks(File primaryfile) throws Exception
+    {
+
+        final int num = packsList.size();
+        sendMsg("Writing " + num + " Pack" + (num > 1 ? "s" : "") + " into installer");
+        Debug.trace("Writing " + num + " Pack" + (num > 1 ? "s" : "") + " into installer");
+        // Map to remember pack number and bytes offsets of back references
+        Map storedFiles = new HashMap();
+
+        // First write the serialized files and file metadata data for each pack
+        // while counting bytes.
+
+        String classname = this.getClass().getName();
+        String volumesize = this.getVariables().getProperty(classname + ".volumesize");
+        String extraspace = this.getVariables().getProperty(classname + ".firstvolumefreespace");
+
+        long volumesizel = FileSpanningOutputStream.DEFAULT_VOLUME_SIZE;
+        long extraspacel = FileSpanningOutputStream.DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE;
+
+        if (volumesize != null)
+        {
+            volumesizel = Long.parseLong(volumesize);
+        }
+        if (extraspace != null)
+        {
+            extraspacel = Long.parseLong(extraspace);
+        }
+        Debug.trace("Volumesize: " + volumesizel);
+        Debug.trace("Extra space on first volume: " + extraspacel);
+        FileSpanningOutputStream fout = new FileSpanningOutputStream(primaryfile.getParent()
+                + File.separator + primaryfile.getName() + ".pak", volumesizel);
+        fout.setFirstvolumefreespacesize(extraspacel);
+
+        int packNumber = 0;
+        Iterator packIter = packsList.iterator();
+        while (packIter.hasNext())
+        {
+            PackInfo packInfo = (PackInfo) packIter.next();
+            Pack pack = packInfo.getPack();
+            pack.nbytes = 0;
+
+            sendMsg("Writing Pack " + packNumber + ": " + pack.name, PackagerListener.MSG_VERBOSE);
+            Debug.trace("Writing Pack " + packNumber + ": " + pack.name);
+            ZipEntry entry = new ZipEntry("packs/pack" + packNumber);
+            // write the metadata as uncompressed object stream to primaryJarStream
+            // ByteCountingOutputStream dos = new
+            // ByteCountingOutputStream(comprStream);
+            // ByteCountingOutputStream dos = new
+            // ByteCountingOutputStream(primaryJarStream);
+            // first write a packs entry
+
+            primaryJarStream.putNextEntry(entry);
+            ObjectOutputStream objOut = new ObjectOutputStream(primaryJarStream);
+
+            // We write the actual pack files
+            objOut.writeInt(packInfo.getPackFiles().size());
+
+            Iterator iter = packInfo.getPackFiles().iterator();
+            while (iter.hasNext())
+            {
+                boolean addFile = !pack.loose;
+                XPackFile pf = new XPackFile((PackFile) iter.next());
+                File file = packInfo.getFile(pf.getPackfile());
+                Debug.trace("Next file: " + file.getAbsolutePath());
+                // use a back reference if file was in previous pack, and in
+                // same jar
+                long[] info = (long[]) storedFiles.get(file);
+                if (info != null && !packJarsSeparate)
+                {
+                    Debug.trace("File already included in other pack");
+                    pf.setPreviousPackFileRef((int) info[0], info[1]);
+                    addFile = false;
+                }
+
+                if (addFile && !pf.isDirectory())
+                {
+                    long pos = fout.getFilepointer();
+
+                    pf.setArchivefileposition(pos);
+
+                    // write out the filepointer
+                    int volumecountbeforewrite = fout.getVolumeCount();
+
+                    FileInputStream inStream = new FileInputStream(file);
+                    long bytesWritten = copyStream(inStream, fout);
+                    fout.flush();
+
+                    long posafterwrite = fout.getFilepointer();
+                    Debug.trace("File (" + pf.sourcePath + ") " + pos + " <-> " + posafterwrite);
+
+                    if (fout.getFilepointer() != (pos + bytesWritten))
+                    {
+                        Debug.trace("file: " + file.getName());
+                        Debug.trace("(Filepos/BytesWritten/ExpectedNewFilePos/NewFilePointer) ("
+                                        + pos + "/" + bytesWritten + "/" + (pos + bytesWritten)
+                                        + "/" + fout.getFilepointer() + ")");
+                        Debug.trace("Volumecount (before/after) ("
+                                + volumecountbeforewrite + "/" + fout.getVolumeCount() + ")");
+                        throw new IOException("Error new filepointer is illegal");
+                    }
+
+                    if (bytesWritten != pf.length()) { throw new IOException(
+                            "File size mismatch when reading " + file); }
+                    inStream.close();
+                    // keine backreferences möglich
+                    // storedFiles.put(file, new long[] { packNumber, pos});
+                }
+
+                objOut.writeObject(pf); // base info
+                objOut.flush(); // make sure it is written
+                // even if not written, it counts towards pack size
+                pack.nbytes += pf.length();
+            }
+            // Write out information about parsable files
+            objOut.writeInt(packInfo.getParsables().size());
+            iter = packInfo.getParsables().iterator();
+            while (iter.hasNext())
+                objOut.writeObject(iter.next());
+
+            // Write out information about executable files
+            objOut.writeInt(packInfo.getExecutables().size());
+            iter = packInfo.getExecutables().iterator();
+            while (iter.hasNext())
+                objOut.writeObject(iter.next());
+
+            // Write out information about updatecheck files
+            objOut.writeInt(packInfo.getUpdateChecks().size());
+            iter = packInfo.getUpdateChecks().iterator();
+            while (iter.hasNext())
+                objOut.writeObject(iter.next());
+
+            // Cleanup
+            objOut.flush();
+            packNumber++;
+        }
+
+        // write metadata for reading in volumes
+        int volumes = fout.getVolumeCount();
+        Debug.trace("Written " + volumes + " volumes");
+        String volumename = primaryfile.getName() + ".pak";
+
+        fout.flush();
+        fout.close();
+
+        primaryJarStream.putNextEntry(new ZipEntry("volumes.info"));
+        ObjectOutputStream out = new ObjectOutputStream(primaryJarStream);
+        out.writeInt(volumes);
+        out.writeUTF(volumename);
+        out.flush();
+        primaryJarStream.closeEntry();
+
+        // Now that we know sizes, write pack metadata to primary jar.
+        primaryJarStream.putNextEntry(new ZipEntry("packs.info"));
+        out = new ObjectOutputStream(primaryJarStream);
+        out.writeInt(packsList.size());
+
+        Iterator i = packsList.iterator();
+        while (i.hasNext())
+        {
+            PackInfo pack = (PackInfo) i.next();
+            out.writeObject(pack.getPack());
+        }
+        out.flush();
+        primaryJarStream.closeEntry();
+    }
+
+    /***********************************************************************************************
+     * Stream utilites for creation of the installer.
+     **********************************************************************************************/
+
+    /** Return a stream for the next jar. */
+    private ZipOutputStream getJarOutputStream(String name) throws IOException
+    {
+        File file = new File(baseFile.getParentFile(), name);
+        sendMsg("Building installer jar: " + file.getAbsolutePath());
+        Debug.trace("Building installer jar: " + file.getAbsolutePath());
+        ZipOutputStream jar = new ZipOutputStream(new FileOutputStream(file));
+        jar.setLevel(Deflater.BEST_COMPRESSION);
+        // jar.setPreventClose(true); // Needed at using FilterOutputStreams which
+        // calls close
+        // of the slave at finalizing.
+
+        return jar;
+    }
+
+    /**
+     * Copies contents of one jar to another.
+     * 
+     * <p>
+     * TODO: it would be useful to be able to keep signature information from signed jar files, can
+     * we combine manifests and still have their content signed?
+     * 
+     * @see #copyStream(InputStream, OutputStream)
+     */
+    private void copyZip(ZipInputStream zin, ZipOutputStream out) throws IOException
+    {
+        copyZip(zin, out, null);
+    }
+
+    /**
+     * Copies specified contents of one jar to another.
+     * 
+     * <p>
+     * TODO: it would be useful to be able to keep signature information from signed jar files, can
+     * we combine manifests and still have their content signed?
+     * 
+     * @see #copyStream(InputStream, OutputStream)
+     */
+    private void copyZip(ZipInputStream zin, ZipOutputStream out, List files) throws IOException
+    {
+        java.util.zip.ZipEntry zentry;
+        if (!alreadyWrittenFiles.containsKey(out)) alreadyWrittenFiles.put(out, new HashSet());
+        HashSet currentSet = (HashSet) alreadyWrittenFiles.get(out);
+        while ((zentry = zin.getNextEntry()) != null)
+        {
+            String currentName = zentry.getName();
+            String testName = currentName.replace('/', '.');
+            testName = testName.replace('\\', '.');
+            if (files != null)
+            {
+                Iterator i = files.iterator();
+                boolean founded = false;
+                while (i.hasNext())
+                { // Make "includes" self to support regex.
+                    String doInclude = (String) i.next();
+                    if (testName.matches(doInclude))
+                    {
+                        founded = true;
+                        break;
+                    }
+                }
+                if (!founded) continue;
+            }
+            if (currentSet.contains(currentName)) continue;
+            try
+            {
+                out.putNextEntry(new ZipEntry(currentName));
+                copyStream(zin, out);
+                out.closeEntry();
+                zin.closeEntry();
+                currentSet.add(currentName);
+            }
+            catch (ZipException x)
+            {
+                // This avoids any problem that can occur with duplicate
+                // directories. for instance all META-INF data in jars
+                // unfortunately this do not work with the apache ZipOutputStream...
+            }
+        }
+    }
+    
+    /**
+     * Copies specified contents of one jar to another without the specified files
+     * 
+     * <p>
+     * TODO: it would be useful to be able to keep signature information from signed jar files, can
+     * we combine manifests and still have their content signed?
+     * 
+     * @see #copyStream(InputStream, OutputStream)
+     */
+    private void copyZipWithoutExcludes(ZipInputStream zin, ZipOutputStream out, List excludes) throws IOException
+    {
+        java.util.zip.ZipEntry zentry;
+        if (!alreadyWrittenFiles.containsKey(out)) alreadyWrittenFiles.put(out, new HashSet());
+        HashSet currentSet = (HashSet) alreadyWrittenFiles.get(out);
+        while ((zentry = zin.getNextEntry()) != null)
+        {
+            String currentName = zentry.getName();
+            String testName = currentName.replace('/', '.');
+            testName = testName.replace('\\', '.');
+            if (excludes != null)
+            {
+                Iterator i = excludes.iterator();
+                boolean skip = false;
+                while (i.hasNext())
+                { 
+                    // Make "excludes" self to support regex.
+                    String doExclude = (String) i.next();                    
+                    if (testName.matches(doExclude))
+                    {                        
+                        skip = true;
+                        break;
+                    }
+                }           
+                if (skip){
+                    continue;
+                }
+            }
+            if (currentSet.contains(currentName)) continue;
+            try
+            {
+                out.putNextEntry(new ZipEntry(currentName));
+                copyStream(zin, out);
+                out.closeEntry();
+                zin.closeEntry();
+                currentSet.add(currentName);
+            }
+            catch (ZipException x)
+            {
+                // This avoids any problem that can occur with duplicate
+                // directories. for instance all META-INF data in jars
+                // unfortunately this do not work with the apache ZipOutputStream...
+            }
+        }
+    }
+
+    /**
+     * Copies all the data from the specified input stream to the specified output stream.
+     * 
+     * @param in the input stream to read
+     * @param out the output stream to write
+     * @return the total number of bytes copied
+     * @exception IOException if an I/O error occurs
+     */
+    private long copyStream(InputStream in, OutputStream out) throws IOException
+    {
+        byte[] buffer = new byte[5120];
+        long bytesCopied = 0;
+        int bytesInBuffer;
+        while ((bytesInBuffer = in.read(buffer)) != -1)
+        {
+            out.write(buffer, 0, bytesInBuffer);
+            bytesCopied += bytesInBuffer;
+        }
+        return bytesCopied;
+    }
+
+    /**
+     * Returns the current pack compressor
+     * 
+     * @return Returns the current pack compressor.
+     */
+    public PackCompressor getCompressor()
+    {
+        return compressor;
+    }
+
+    public void setCompressorOptions(String compr_format, int compr_level) throws CompilerException
+    {
+        compressor = PackCompressorFactory.get(compr_format);
+        compressor.setCompressionLevel(compr_level);
+    }
+
+    public void addConfigurationInformation(XMLElement data)
+    {
+       this.configdata = data;        
+    }
+
+    public void initPackCompressor(String compr_format, int compr_level) throws CompilerException
+    {       
+        this.setCompressorOptions(compr_format, compr_level);        
+    }
+}
\ No newline at end of file

Added: izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeInstaller.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeInstaller.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeInstaller.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,117 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.installer;
+
+import java.io.File;
+import java.lang.reflect.Method;
+
+import com.izforge.izpack.uninstaller.SelfModifier;
+import com.izforge.izpack.util.Debug;
+
+/**
+ * Main class, for starting the installer if it was build to support more than one volume.
+ * 
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ * 
+ */
+public class MultiVolumeInstaller
+{
+
+    // where is the installer looking for media files
+    protected static String mediadirectory;
+
+    /**
+     * @param args
+     */
+    public static void main(String[] args)
+    {
+        // default is to look in the current directory
+        MultiVolumeInstaller.setMediadirectory(new File(".").getParent());
+        if ((args.length > 0) && ("-direct".equals(args[0])))
+        {
+            String[] newargs;
+            if (args.length > 1)
+            {
+                // cut out the direct parameter
+                newargs = new String[args.length - 1];
+                System.arraycopy(args, 1, newargs, 0, args.length - 1);
+            }
+            else
+            {
+                // set arguments to empty string array
+                newargs = new String[0];
+            }
+            MultiVolumeInstaller.install(newargs);
+        }
+        else
+        {
+            try
+            {
+                Class clazz = MultiVolumeInstaller.class;
+                Method target = clazz.getMethod("install", new Class[] { String[].class});
+                String[] newargs = new String[args.length + 2];
+
+                System.arraycopy(args, 0, newargs, 2, args.length);
+                // try to find the directory, where the jar file is located, this class was loaded
+                // from
+                newargs[0] = "-mediadir";
+                newargs[1] = SelfModifier.findJarFile(clazz).getParent();
+                System.out.println("Setting mediadir: " + newargs[1]);
+                MultiVolumeInstaller.setMediadirectory(SelfModifier.findJarFile(clazz).getParent());
+                new SelfModifier(target).invoke(newargs);
+            }
+            catch (Exception e)
+            {
+                Debug.trace(e);
+            }
+        }
+    }
+
+    public static String getMediadirectory()
+    {
+        return MultiVolumeInstaller.mediadirectory;
+    }
+
+    public static void setMediadirectory(String mediadirectory)
+    {
+        MultiVolumeInstaller.mediadirectory = mediadirectory;
+    }
+
+    public static void install(String[] args)
+    {
+        if ((args.length >= 2) && ("-mediadir".equals(args[0])))
+        {
+            // mediadir option given
+            MultiVolumeInstaller.setMediadirectory(args[1]);
+            if (args.length > 2)
+            {
+                // cut out this option
+                String[] newargs = new String[args.length - 2];
+                System.arraycopy(args, 2, newargs, 0, args.length - 2);
+                args = newargs;
+            }
+            else
+            {
+                // put in an empty string array
+                args = new String[0];
+            }
+        }
+        // just call the izpack installer
+        Installer.main(args);
+    }
+}

Added: izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeUnpacker.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeUnpacker.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/installer/MultiVolumeUnpacker.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,1327 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.installer;
+
+import java.io.BufferedOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Stack;
+import java.util.TreeSet;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.regexp.RE;
+import org.apache.regexp.RECompiler;
+import org.apache.regexp.RESyntaxException;
+
+import com.izforge.izpack.ExecutableFile;
+import com.izforge.izpack.LocaleDatabase;
+import com.izforge.izpack.Pack;
+import com.izforge.izpack.PackFile;
+import com.izforge.izpack.ParsableFile;
+import com.izforge.izpack.UpdateCheck;
+import com.izforge.izpack.XPackFile;
+import com.izforge.izpack.event.InstallerListener;
+import com.izforge.izpack.installer.AutomatedInstallData;
+import com.izforge.izpack.installer.InstallerFrame;
+import com.izforge.izpack.installer.IzPanel;
+import com.izforge.izpack.installer.ResourceManager;
+import com.izforge.izpack.installer.ScriptParser;
+import com.izforge.izpack.installer.UninstallData;
+import com.izforge.izpack.io.FileSpanningInputStream;
+import com.izforge.izpack.io.FileSpanningOutputStream;
+import com.izforge.izpack.io.VolumeNotFoundException;
+import com.izforge.izpack.panels.NextMediaDialog;
+import com.izforge.izpack.util.AbstractUIHandler;
+import com.izforge.izpack.util.AbstractUIProgressHandler;
+import com.izforge.izpack.util.Debug;
+import com.izforge.izpack.util.FileExecutor;
+import com.izforge.izpack.util.IoHelper;
+import com.izforge.izpack.util.OsConstraint;
+import com.izforge.izpack.util.VariableSubstitutor;
+
+/**
+ * Unpacker class.
+ * 
+ * @author Dennis Reil
+ */
+public class MultiVolumeUnpacker implements IUnpacker
+{
+
+    /** The installdata. */
+    private AutomatedInstallData idata;
+
+    /** The installer listener. */
+    private AbstractUIProgressHandler handler;
+
+    /** The uninstallation data. */
+    private UninstallData udata;
+
+    /** The variables substitutor. */
+    private VariableSubstitutor vs;
+
+    /** The instances of the unpacker objects. */
+    private static HashMap instances = new HashMap();
+
+    /** The absolute path of the installation. (NOT the canonical!) */
+    private File absolute_installpath;
+
+    /** The packs locale database. */
+    private LocaleDatabase langpack = null;
+
+    /** Interrupt flag if global interrupt is desired. */
+    private static boolean interruptDesired = false;
+
+    /** Do not perform a interrupt call. */
+    private static boolean discardInterrupt = false;
+
+    /** The name of the XML file that specifies the panel langpack */
+    private static final String LANG_FILE_NAME = "packsLang.xml";
+
+    public static final String ALIVE = "alive";
+
+    public static final String INTERRUPT = "doInterrupt";
+
+    public static final String INTERRUPTED = "interruppted";
+
+    /** The result of the operation. */
+    private boolean result = true;
+
+    /**
+     * The constructor.
+     * 
+     * @param idata The installation data.
+     * @param handler The installation progress handler.
+     */
+    public MultiVolumeUnpacker(AutomatedInstallData idata, AbstractUIProgressHandler handler)
+    {        
+        try
+        {
+            String resource = LANG_FILE_NAME + "_" + idata.localeISO3;
+            this.langpack = new LocaleDatabase(ResourceManager.getInstance().getInputStream(
+                    resource));
+        }
+        catch (Throwable exception)
+        {}
+
+        this.idata = idata;
+        this.handler = handler;
+
+        // Initialize the variable substitutor
+        vs = new VariableSubstitutor(idata.getVariables());
+    }
+
+    /**
+     * Returns a copy of the active unpacker instances.
+     * 
+     * @return a copy of active unpacker instances
+     */
+    public static HashMap getRunningInstances()
+    {
+        synchronized (instances)
+        { // Return a shallow copy to prevent a
+            // ConcurrentModificationException.
+            return (HashMap) (instances.clone());
+        }
+    }
+
+    /**
+     * Adds this to the map of all existent instances of Unpacker.
+     */
+    private void addToInstances()
+    {
+        synchronized (instances)
+        {
+            instances.put(this, ALIVE);
+        }
+    }
+
+    /**
+     * Removes this from the map of all existent instances of Unpacker.
+     */
+    private void removeFromInstances()
+    {
+        synchronized (instances)
+        {
+            instances.remove(this);
+        }
+    }
+
+    /**
+     * Initiate interrupt of all alive Unpacker. This method does not interrupt the Unpacker objects
+     * else it sets only the interrupt flag for the Unpacker objects. The dispatching of interrupt
+     * will be performed by the Unpacker objects self.
+     */
+    private static void setInterruptAll()
+    {
+        synchronized (instances)
+        {
+            Iterator iter = instances.keySet().iterator();
+            while (iter.hasNext())
+            {
+                Object key = iter.next();
+                if (instances.get(key).equals(ALIVE))
+                {
+                    instances.put(key, INTERRUPT);
+                }
+            }
+            // Set global flag to allow detection of it in other classes.
+            // Do not set it to thread because an exec will then be stoped.
+            setInterruptDesired(true);
+        }
+    }
+
+    /**
+     * Initiate interrupt of all alive Unpacker and waits until all Unpacker are interrupted or the
+     * wait time has arrived. If the doNotInterrupt flag in InstallerListener is set to true, the
+     * interrupt will be discarded.
+     * 
+     * @param waitTime wait time in millisecounds
+     * @return true if the interrupt will be performed, false if the interrupt will be discarded
+     */
+    public static boolean interruptAll(long waitTime)
+    {
+        long t0 = System.currentTimeMillis();
+        if (isDiscardInterrupt()) return (false);
+        setInterruptAll();
+        while (!isInterruptReady())
+        {
+            if (System.currentTimeMillis() - t0 > waitTime) return (true);
+            try
+            {
+                Thread.sleep(100);
+            }
+            catch (InterruptedException e)
+            {}
+        }
+        return (true);
+    }
+
+    private static boolean isInterruptReady()
+    {
+        synchronized (instances)
+        {
+            Iterator iter = instances.keySet().iterator();
+            while (iter.hasNext())
+            {
+                Object key = iter.next();
+                if (!instances.get(key).equals(INTERRUPTED)) return (false);
+            }
+            return (true);
+        }
+
+    }
+
+    /**
+     * Sets the interrupt flag for this Unpacker to INTERRUPTED if the previos state was INTERRUPT
+     * or INTERRUPTED and returns whether interrupt was initiate or not.
+     * 
+     * @return whether interrupt was initiate or not
+     */
+    private boolean performInterrupted()
+    {
+        synchronized (instances)
+        {
+            Object doIt = instances.get(this);
+            if (doIt != null && (doIt.equals(INTERRUPT) || doIt.equals(INTERRUPTED)))
+            {
+                instances.put(this, INTERRUPTED);
+                this.result = false;
+                return (true);
+            }
+            return (false);
+        }
+    }
+
+    /**
+     * Returns whether interrupt was initiate or not for this Unpacker.
+     * 
+     * @return whether interrupt was initiate or not
+     */
+    private boolean shouldInterrupt()
+    {
+        synchronized (instances)
+        {
+            Object doIt = instances.get(this);
+            if (doIt != null && (doIt.equals(INTERRUPT) || doIt.equals(INTERRUPTED))) { return (true); }
+            return (false);
+        }
+
+    }
+
+    protected File enterNextMediaMessage(String volumename)
+    {
+        Debug.trace("Enter next media: " + volumename);
+        StackTraceElement[] el = (new Exception()).getStackTrace();
+        for (int i = 0; i < el.length; i++)
+        {
+            StackTraceElement element = el[i];
+            Debug.trace(element.toString());
+        }
+
+        File nextvolume = new File(volumename);
+        NextMediaDialog nmd = null;
+        while (!nextvolume.exists())
+        {
+            if ((this.handler != null) && (this.handler instanceof IzPanel))
+            {
+                InstallerFrame installframe = ((IzPanel) this.handler).getInstallerFrame();
+                nmd = new NextMediaDialog(installframe, idata, volumename);
+            }
+            else
+            {
+                nmd = new NextMediaDialog(null, idata, volumename);
+            }
+            nmd.setVisible(true);
+            String nextmediainput = nmd.getNextMedia();
+            if (nextmediainput != null)
+            {
+                nextvolume = new File(nextmediainput);
+            }
+            else
+            {
+                Debug.trace("Input from NextMediaDialog was null");
+                nextvolume = new File(volumename);
+            }
+        }
+        return nextvolume;
+    }
+
+    /** The run method. */
+    public void run()
+    {
+        addToInstances();
+        try
+        {
+            //
+            // Initialisations
+            FileOutputStream out = null;
+            ArrayList parsables = new ArrayList();
+            ArrayList executables = new ArrayList();
+            ArrayList updatechecks = new ArrayList();
+            List packs = idata.selectedPacks;
+            int npacks = packs.size();
+            Debug.trace("Unpacker starting");
+            handler.startAction("Unpacking", npacks);
+            udata = UninstallData.getInstance();
+            // Custom action listener stuff --- load listeners ----
+            List[] customActions = getCustomActions();
+            // Custom action listener stuff --- beforePacks ----
+            informListeners(customActions, InstallerListener.BEFORE_PACKS, idata, new Integer(
+                    npacks), handler);
+            // vs = new VariableSubstitutor(idata.getVariables());
+            packs = idata.selectedPacks;
+            npacks = packs.size();
+            if (npacks == 0)
+            {
+                if (performInterrupted())
+                { // Interrupt was initiated; perform it.
+                    return;
+                }
+
+                // Custom action listener stuff --- afterPacks ----
+                informListeners(customActions, InstallerListener.AFTER_PACKS, idata, handler, null);
+                if (performInterrupted())
+                { // Interrupt was initiated; perform it.
+                    return;
+                }
+
+                // The end :-)
+                handler.stopAction();
+                return;
+            }
+            InputStream in = MultiVolumeUnpacker.class
+                    .getResourceAsStream(FileSpanningOutputStream.VOLUMES_INFO);
+            // get volumes metadata
+            ObjectInputStream metadataobj = new ObjectInputStream(in);
+            // TODO: create MetadataObject
+            int volumes = metadataobj.readInt();
+            String volumename = metadataobj.readUTF();
+            Debug.trace("Reading from " + volumes + " volumes with basename " + volumename + " ");
+            metadataobj.close();
+            String mediadirectory = MultiVolumeInstaller.getMediadirectory();
+            if ((mediadirectory == null) || (mediadirectory.length() <= 0))
+            {
+                Debug.trace("Mediadirectory wasn't set.");
+                mediadirectory = System.getProperty("java.io.tmpdir"); // try the temporary
+                // directory
+            }
+            Debug.trace("Using mediadirectory = " + mediadirectory);
+            File volume = new File(mediadirectory + File.separator + volumename);
+            if (!volume.exists())
+            {
+                volume = enterNextMediaMessage(volume.getAbsolutePath());
+            }
+            FileSpanningInputStream fin = new FileSpanningInputStream(volume, volumes);
+
+            // We unpack the selected packs
+            for (int i = 0; i < npacks; i++)
+            {
+                // We get the pack stream
+                int n = idata.allPacks.indexOf(packs.get(i));
+
+                in = MultiVolumeUnpacker.class.getResourceAsStream("/packs/pack" + n);
+
+                // Custom action listener stuff --- beforePack ----
+                informListeners(customActions, InstallerListener.BEFORE_PACK, packs.get(i),
+                        new Integer(npacks), handler);
+                // find next Entry
+                ObjectInputStream objIn = new ObjectInputStream(in);
+                // We unpack the files
+                int nfiles = objIn.readInt();
+
+                // We get the internationalized name of the pack
+                final Pack pack = ((Pack) packs.get(i));
+                String stepname = pack.name;// the message to be passed to the
+                // installpanel
+                if (langpack != null && !(pack.id == null || "".equals(pack.id)))
+                {
+
+                    final String name = langpack.getString(pack.id);
+                    if (name != null && !"".equals(name))
+                    {
+                        stepname = name;
+                    }
+                }
+                handler.nextStep(stepname, i + 1, nfiles);
+                for (int j = 0; j < nfiles; j++)
+                {
+                    // We read the header
+                    XPackFile pf = (XPackFile) objIn.readObject();
+
+                    if (OsConstraint.oneMatchesCurrentSystem(pf.osConstraints()))
+                    {
+                        // We translate & build the path
+                        String path = IoHelper.translatePath(pf.getTargetPath(), vs);
+                        File pathFile = new File(path);
+                        File dest = pathFile;
+                        if (!pf.isDirectory()) dest = pathFile.getParentFile();
+
+                        if (!dest.exists())
+                        {
+                            // If there are custom actions which would be called
+                            // at
+                            // creating a directory, create it recursively.
+                            List fileListeners = customActions[customActions.length - 1];
+                            if (fileListeners != null && fileListeners.size() > 0)
+                                mkDirsWithEnhancement(dest, pf, customActions);
+                            else
+                            // Create it in on step.
+                            {
+                                if (!dest.mkdirs())
+                                {
+                                    handler.emitError("Error creating directories",
+                                            "Could not create directory\n" + dest.getPath());
+                                    handler.stopAction();
+                                    this.result = false;
+                                    return;
+                                }
+                            }
+                        }
+
+                        if (pf.isDirectory()) continue;
+
+                        // Custom action listener stuff --- beforeFile ----
+                        informListeners(customActions, InstallerListener.BEFORE_FILE, pathFile, pf,
+                                null);
+                        // We add the path to the log,
+                        udata.addFile(path);
+
+                        handler.progress(j, path);
+
+                        // if this file exists and should not be overwritten,
+                        // check
+                        // what to do
+                        if ((pathFile.exists()) && (pf.override() != PackFile.OVERRIDE_TRUE))
+                        {
+                            boolean overwritefile = false;
+
+                            // don't overwrite file if the user said so
+                            if (pf.override() != PackFile.OVERRIDE_FALSE)
+                            {
+                                if (pf.override() == PackFile.OVERRIDE_TRUE)
+                                {
+                                    overwritefile = true;
+                                }
+                                else if (pf.override() == PackFile.OVERRIDE_UPDATE)
+                                {
+                                    // check mtime of involved files
+                                    // (this is not 100% perfect, because the
+                                    // already existing file might
+                                    // still be modified but the new installed
+                                    // is just a bit newer; we would
+                                    // need the creation time of the existing
+                                    // file or record with which mtime
+                                    // it was installed...)
+                                    overwritefile = (pathFile.lastModified() < pf.lastModified());
+                                }
+                                else
+                                {
+                                    int def_choice = -1;
+
+                                    if (pf.override() == PackFile.OVERRIDE_ASK_FALSE)
+                                        def_choice = AbstractUIHandler.ANSWER_NO;
+                                    if (pf.override() == PackFile.OVERRIDE_ASK_TRUE)
+                                        def_choice = AbstractUIHandler.ANSWER_YES;
+
+                                    int answer = handler.askQuestion(idata.langpack
+                                            .getString("InstallPanel.overwrite.title")
+                                            + " - " + pathFile.getName(), idata.langpack
+                                            .getString("InstallPanel.overwrite.question")
+                                            + pathFile.getAbsolutePath(),
+                                            AbstractUIHandler.CHOICES_YES_NO, def_choice);
+
+                                    overwritefile = (answer == AbstractUIHandler.ANSWER_YES);
+                                }
+
+                            }
+
+                            if (!overwritefile)
+                            {
+                                if (!pf.isBackReference() && !((Pack) packs.get(i)).loose)
+                                {
+                                    // objIn.skip(pf.length());
+                                }
+                                continue;
+                            }
+
+                        }
+
+                        // We copy the file
+                        out = new FileOutputStream(pathFile);
+                        byte[] buffer = new byte[5120];
+                        long bytesCopied = 0;
+                        // InputStream pis = objIn;
+                        InputStream pis = fin;
+
+                        if (((Pack) packs.get(i)).loose)
+                        {
+                            pis = new FileInputStream(pf.sourcePath);
+                        }
+
+                        // read in the position of this file
+                        // long fileposition = objIn.readLong();
+                        long fileposition = pf.getArchivefileposition();
+
+                        while (fin.getFilepointer() < fileposition)
+                        {
+                            // we have to skip some bytes
+                            Debug.trace("Skipping bytes to get to file " + pathFile.getName()
+                                    + " (" + fin.getFilepointer() + "<" + fileposition
+                                    + ") target is: " + (fileposition - fin.getFilepointer()));
+                            try
+                            {
+                                fin.skip(fileposition - fin.getFilepointer());
+                                break;
+                            }
+                            catch (VolumeNotFoundException vnfe)
+                            {
+                                File nextmedia = enterNextMediaMessage(vnfe.getVolumename());
+                                fin.setVolumename(nextmedia.getAbsolutePath());
+                            }
+                        }
+
+                        if (fin.getFilepointer() > fileposition)
+                        {
+                            Debug.trace("Error, can't access file in pack.");
+                        }
+
+                        while (bytesCopied < pf.length())
+                        {
+                            try
+                            {
+                                if (performInterrupted())
+                                { // Interrupt was initiated; perform it.
+                                    out.close();
+                                    if (pis != objIn) pis.close();
+                                    return;
+                                }
+                                int maxBytes = (int) Math.min(pf.length() - bytesCopied,
+                                        buffer.length);
+
+                                int bytesInBuffer = pis.read(buffer, 0, maxBytes);
+                                if (bytesInBuffer == -1)
+                                {
+                                    Debug.trace("Unexpected end of stream (installer corrupted?)");
+                                    throw new IOException(
+                                            "Unexpected end of stream (installer corrupted?)");
+                                }
+
+                                out.write(buffer, 0, bytesInBuffer);
+
+                                bytesCopied += bytesInBuffer;
+                            }
+                            catch (VolumeNotFoundException vnfe)
+                            {
+                                File nextmedia = enterNextMediaMessage(vnfe.getVolumename());
+                                fin.setVolumename(nextmedia.getAbsolutePath());
+                            }
+                        }
+                        // Cleanings
+                        out.close();
+                        // if (pis != objIn) pis.close();
+
+                        // Set file modification time if specified
+                        if (pf.lastModified() >= 0) pathFile.setLastModified(pf.lastModified());
+                        // Custom action listener stuff --- afterFile ----
+                        informListeners(customActions, InstallerListener.AFTER_FILE, pathFile, pf,
+                                null);
+                    }
+                    else
+                    {
+                        if (!pf.isBackReference())
+                        {
+                            // objIn.skip(pf.length());
+                        }
+                    }
+                }
+                // Load information about parsable files
+                int numParsables = objIn.readInt();
+                Debug.trace("Looking for parsables");
+                for (int k = 0; k < numParsables; k++)
+                {
+                    ParsableFile pf = null;
+                    while (true)
+                    {
+                        try
+                        {
+                            pf = (ParsableFile) objIn.readObject();
+                            break;
+                        }
+                        catch (VolumeNotFoundException vnfe)
+                        {
+                            File nextmedia = enterNextMediaMessage(vnfe.getVolumename());
+                            fin.setVolumename(nextmedia.getAbsolutePath());
+                        }
+                        catch (EOFException eofe)
+                        {
+                            File nextmedia = enterNextMediaMessage("");
+                            fin.setVolumename(nextmedia.getAbsolutePath());
+                        }
+                    }
+                    pf.path = IoHelper.translatePath(pf.path, vs);
+                    Debug.trace("Found parsable: " + pf.path);
+                    parsables.add(pf);
+                }
+
+                // Load information about executable files
+                int numExecutables = objIn.readInt();
+                Debug.trace("Looking for executables...");
+                for (int k = 0; k < numExecutables; k++)
+                {
+                    ExecutableFile ef = (ExecutableFile) objIn.readObject();
+
+                    ef.path = IoHelper.translatePath(ef.path, vs);
+                    if (null != ef.argList && !ef.argList.isEmpty())
+                    {
+                        String arg = null;
+                        for (int j = 0; j < ef.argList.size(); j++)
+                        {
+                            arg = (String) ef.argList.get(j);
+                            arg = IoHelper.translatePath(arg, vs);
+                            ef.argList.set(j, arg);
+                        }
+                    }
+                    Debug.trace("Found executable: " + ef.path);
+                    executables.add(ef);
+                    if (ef.executionStage == ExecutableFile.UNINSTALL)
+                    {
+                        udata.addExecutable(ef);
+                    }
+                }
+                // Custom action listener stuff --- uninstall data ----
+                handleAdditionalUninstallData(udata, customActions);
+
+                // Load information about updatechecks
+                int numUpdateChecks = objIn.readInt();
+                Debug.trace("Looking for updatechecks");
+                for (int k = 0; k < numUpdateChecks; k++)
+                {
+                    UpdateCheck uc = (UpdateCheck) objIn.readObject();
+                    Debug.trace("found updatecheck");
+                    updatechecks.add(uc);
+                }
+
+                // objIn.close();
+
+                if (performInterrupted())
+                { // Interrupt was initiated; perform it.
+                    return;
+                }
+
+                // Custom action listener stuff --- afterPack ----
+                informListeners(customActions, InstallerListener.AFTER_PACK, packs.get(i),
+                        new Integer(i), handler);
+            }
+            Debug.trace("Trying to parse files");
+            // We use the scripts parser
+            ScriptParser parser = new ScriptParser(parsables, vs);
+            parser.parseFiles();
+            Debug.trace("parsed files");
+            if (performInterrupted())
+            { // Interrupt was initiated; perform it.
+                return;
+            }
+            Debug.trace("Trying to execute files");
+            // We use the file executor
+            FileExecutor executor = new FileExecutor(executables);
+            if (executor.executeFiles(ExecutableFile.POSTINSTALL, handler) != 0)
+            {
+                handler.emitError("File execution failed", "The installation was not completed");
+                this.result = false;
+                Debug.trace("File execution failed");
+            }
+
+            if (performInterrupted())
+            { // Interrupt was initiated; perform it.
+                return;
+            }
+            Debug.trace("Create uninstaller");
+            // We put the uninstaller (it's not yet complete...)
+            putUninstaller();
+            Debug.trace("Uninstaller created");
+            // update checks _after_ uninstaller was put, so we don't delete it
+            Debug.trace("Perform updateChecks");
+            performUpdateChecks(updatechecks);
+            Debug.trace("updatechecks performed.");
+            if (performInterrupted())
+            { // Interrupt was initiated; perform it.
+                return;
+            }
+
+            // Custom action listener stuff --- afterPacks ----
+            informListeners(customActions, InstallerListener.AFTER_PACKS, idata, handler, null);
+            if (performInterrupted())
+            { // Interrupt was initiated; perform it.
+                return;
+            }
+            this.writeConfigInformation();
+            // The end :-)
+            handler.stopAction();
+            Debug.trace("Installation complete");
+        }
+        catch (Exception err)
+        {
+            // TODO: finer grained error handling with useful error messages
+            handler.stopAction();
+            handler.emitError("An error occured", err.toString());
+            err.printStackTrace();
+            Debug.trace("Error while installing: " + err.toString());
+            this.result = false;
+        }
+        finally
+        {
+            removeFromInstances();
+        }
+    }
+
+    protected void writeConfigInformation()
+    {
+        // save the variables
+        Properties installerproperties = idata.getVariables();
+        Enumeration installerpropertieskeys = installerproperties.keys();
+        try
+        {
+            String installpath = idata.getVariable("INSTALL_PATH");
+            PrintWriter pw = new PrintWriter(new FileOutputStream(installpath + File.separator
+                    + "installer.properties"));
+            pw.println("# Installer properties, written by MultiVolumeUnpacker.");
+            while (installerpropertieskeys.hasMoreElements())
+            {
+                String key = (String) installerpropertieskeys.nextElement();
+                if (key.startsWith("SYSTEM_"))
+                {
+                    // skip
+                    continue;
+                }
+                else if (key.startsWith("password_"))
+                {
+                    // skip
+                    continue;
+                }
+                pw.println(key + "=" + installerproperties.getProperty(key));
+            }
+            pw.flush();
+            pw.close();
+        }
+        catch (Exception e)
+        {
+            Debug.trace("Error while writing config information in MultiVolumeUnpacker: "
+                    + e.getMessage());
+        }
+    }
+
+    /**
+     * Return the state of the operation.
+     * 
+     * @return true if the operation was successful, false otherwise.
+     */
+    public boolean getResult()
+    {
+        return this.result;
+    }
+
+    /**
+     * @param updatechecks
+     */
+    private void performUpdateChecks(ArrayList updatechecks)
+    {
+        ArrayList include_patterns = new ArrayList();
+        ArrayList exclude_patterns = new ArrayList();
+
+        RECompiler recompiler = new RECompiler();
+
+        this.absolute_installpath = new File(idata.getInstallPath()).getAbsoluteFile();
+
+        // at first, collect all patterns
+        for (Iterator iter = updatechecks.iterator(); iter.hasNext();)
+        {
+            UpdateCheck uc = (UpdateCheck) iter.next();
+
+            if (uc.includesList != null)
+                include_patterns.addAll(preparePatterns(uc.includesList, recompiler));
+
+            if (uc.excludesList != null)
+                exclude_patterns.addAll(preparePatterns(uc.excludesList, recompiler));
+        }
+
+        // do nothing if no update checks were specified
+        if (include_patterns.size() == 0) return;
+
+        // now collect all files in the installation directory and figure
+        // out files to check for deletion
+
+        // use a treeset for fast access
+        TreeSet installed_files = new TreeSet();
+
+        for (Iterator if_it = this.udata.getFilesList().iterator(); if_it.hasNext();)
+        {
+            String fname = (String) if_it.next();
+
+            File f = new File(fname);
+
+            if (!f.isAbsolute())
+            {
+                f = new File(this.absolute_installpath, fname);
+            }
+
+            installed_files.add(f.getAbsolutePath());
+        }
+
+        // now scan installation directory (breadth first), contains Files of
+        // directories to scan
+        // (note: we'll recurse infinitely if there are circular links or
+        // similar nasty things)
+        Stack scanstack = new Stack();
+
+        // contains File objects determined for deletion
+        ArrayList files_to_delete = new ArrayList();
+
+        try
+        {
+            scanstack.add(absolute_installpath);
+
+            while (!scanstack.empty())
+            {
+                File f = (File) scanstack.pop();
+
+                File[] files = f.listFiles();
+
+                if (files == null) { throw new IOException(f.getPath() + "is not a directory!"); }
+
+                for (int i = 0; i < files.length; i++)
+                {
+                    File newf = files[i];
+
+                    String newfname = newf.getPath();
+
+                    // skip files we just installed
+                    if (installed_files.contains(newfname)) continue;
+
+                    if (fileMatchesOnePattern(newfname, include_patterns)
+                            && (!fileMatchesOnePattern(newfname, exclude_patterns)))
+                    {
+                        files_to_delete.add(newf);
+                    }
+
+                    if (newf.isDirectory())
+                    {
+                        scanstack.push(newf);
+                    }
+
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            this.handler.emitError("error while performing update checks", e.toString());
+        }
+
+        for (Iterator f_it = files_to_delete.iterator(); f_it.hasNext();)
+        {
+            File f = (File) f_it.next();
+
+            if (!f.isDirectory())
+            // skip directories - they cannot be removed safely yet
+            {
+                this.handler.emitNotification("deleting " + f.getPath());
+                f.delete();
+            }
+
+        }
+
+    }
+
+    /**
+     * @param filename
+     * @param patterns
+     * 
+     * @return true if the file matched one pattern, false if it did not
+     */
+    private boolean fileMatchesOnePattern(String filename, ArrayList patterns)
+    {
+        // first check whether any include matches
+        for (Iterator inc_it = patterns.iterator(); inc_it.hasNext();)
+        {
+            RE pattern = (RE) inc_it.next();
+
+            if (pattern.match(filename)) { return true; }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param list A list of file name patterns (in ant fileset syntax)
+     * @param recompiler The regular expression compiler (used to speed up RE compiling).
+     * 
+     * @return List of org.apache.regexp.RE
+     */
+    private List preparePatterns(ArrayList list, RECompiler recompiler)
+    {
+        ArrayList result = new ArrayList();
+
+        for (Iterator iter = list.iterator(); iter.hasNext();)
+        {
+            String element = (String) iter.next();
+
+            if ((element != null) && (element.length() > 0))
+            {
+                // substitute variables in the pattern
+                element = this.vs.substitute(element, "plain");
+
+                // check whether the pattern is absolute or relative
+                File f = new File(element);
+
+                // if it is relative, make it absolute and prepend the
+                // installation path
+                // (this is a bit dangerous...)
+                if (!f.isAbsolute())
+                {
+                    element = new File(this.absolute_installpath, element).toString();
+                }
+
+                // now parse the element and construct a regular expression from
+                // it
+                // (we have to parse it one character after the next because
+                // every
+                // character should only be processed once - it's not possible
+                // to get this
+                // correct using regular expression replacing)
+                StringBuffer element_re = new StringBuffer();
+
+                int lookahead = -1;
+
+                int pos = 0;
+
+                while (pos < element.length())
+                {
+                    char c;
+
+                    if (lookahead != -1)
+                    {
+                        c = (char) lookahead;
+                        lookahead = -1;
+                    }
+                    else
+                        c = element.charAt(pos++);
+
+                    switch (c)
+                    {
+                    case '/': {
+                        element_re.append(File.separator);
+                        break;
+                    }
+                        // escape backslash and dot
+                    case '\\':
+                    case '.': {
+                        element_re.append("\\");
+                        element_re.append(c);
+                        break;
+                    }
+                    case '*': {
+                        if (pos == element.length())
+                        {
+                            element_re.append("[^").append(File.separator).append("]*");
+                            break;
+                        }
+
+                        lookahead = element.charAt(pos++);
+
+                        // check for "**"
+                        if (lookahead == '*')
+                        {
+                            element_re.append(".*");
+                            // consume second star
+                            lookahead = -1;
+                        }
+                        else
+                        {
+                            element_re.append("[^").append(File.separator).append("]*");
+                            // lookahead stays there
+                        }
+                        break;
+                    }
+                    default: {
+                        element_re.append(c);
+                        break;
+                    }
+                    } // switch
+
+                }
+
+                // make sure that the whole expression is matched
+                element_re.append('$');
+
+                // replace \ by \\ and create a RE from the result
+                try
+                {
+                    result.add(new RE(recompiler.compile(element_re.toString())));
+                }
+                catch (RESyntaxException e)
+                {
+                    this.handler.emitNotification("internal error: pattern \"" + element
+                            + "\" produced invalid RE \"" + f.getPath() + "\"");
+                }
+
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Puts the uninstaller.
+     * 
+     * @exception Exception Description of the Exception
+     */
+    private void putUninstaller() throws Exception
+    {
+        // get the uninstaller base, returning if not found so that
+        // idata.uninstallOutJar remains null
+        InputStream[] in = new InputStream[2];
+        in[0] = MultiVolumeUnpacker.class.getResourceAsStream("/res/IzPack.uninstaller");
+        if (in[0] == null) return;
+        // The uninstaller extension is facultative; it will be exist only
+        // if a native library was marked for uninstallation.
+        in[1] = MultiVolumeUnpacker.class.getResourceAsStream("/res/IzPack.uninstaller-ext");
+
+        // Me make the .uninstaller directory
+        String dest = IoHelper.translatePath("$INSTALL_PATH", vs) + File.separator + "Uninstaller";
+        String jar = dest + File.separator + idata.info.getUninstallerName();
+        File pathMaker = new File(dest);
+        pathMaker.mkdirs();
+
+        // We log the uninstaller deletion information
+        udata.setUninstallerJarFilename(jar);
+        udata.setUninstallerPath(dest);
+
+        // We open our final jar file
+        FileOutputStream out = new FileOutputStream(jar);
+        // Intersect a buffer else byte for byte will be written to the file.
+        BufferedOutputStream bos = new BufferedOutputStream(out);
+        ZipOutputStream outJar = new ZipOutputStream(bos);
+        idata.uninstallOutJar = outJar;
+        outJar.setLevel(9);
+        udata.addFile(jar);
+
+        // We copy the uninstallers
+        HashSet doubles = new HashSet();
+
+        for (int i = 0; i < in.length; ++i)
+        {
+            if (in[i] == null) continue;
+            ZipInputStream inRes = new ZipInputStream(in[i]);
+            ZipEntry zentry = inRes.getNextEntry();
+            while (zentry != null)
+            {
+                // Puts a new entry, but not twice like META-INF
+                if (!doubles.contains(zentry.getName()))
+                {
+                    doubles.add(zentry.getName());
+                    outJar.putNextEntry(new ZipEntry(zentry.getName()));
+
+                    // Byte to byte copy
+                    int unc = inRes.read();
+                    while (unc != -1)
+                    {
+                        outJar.write(unc);
+                        unc = inRes.read();
+                    }
+
+                    // Next one please
+                    inRes.closeEntry();
+                    outJar.closeEntry();
+                }
+                zentry = inRes.getNextEntry();
+            }
+            inRes.close();
+        }
+
+        // We put the langpack
+        InputStream in2 = MultiVolumeUnpacker.class.getResourceAsStream("/langpacks/"
+                + idata.localeISO3 + ".xml");
+        outJar.putNextEntry(new ZipEntry("langpack.xml"));
+        int read = in2.read();
+        while (read != -1)
+        {
+            outJar.write(read);
+            read = in2.read();
+        }
+        outJar.closeEntry();
+    }
+
+    // CUSTOM ACTION STUFF -------------- start -----------------
+
+    /**
+     * Informs all listeners which would be informed at the given action type.
+     * 
+     * @param customActions array of lists with the custom action objects
+     * @param action identifier for which callback should be called
+     * @param firstParam first parameter for the call
+     * @param secondParam second parameter for the call
+     * @param thirdParam third parameter for the call
+     */
+    private void informListeners(List[] customActions, int action, Object firstParam,
+            Object secondParam, Object thirdParam) throws Exception
+    {
+        List listener = null;
+        // select the right action list.
+        switch (action)
+        {
+        case InstallerListener.BEFORE_FILE:
+        case InstallerListener.AFTER_FILE:
+        case InstallerListener.BEFORE_DIR:
+        case InstallerListener.AFTER_DIR:
+            listener = customActions[customActions.length - 1];
+            break;
+        default:
+            listener = customActions[0];
+            break;
+        }
+        if (listener == null) return;
+        // Iterate the action list.
+        Iterator iter = listener.iterator();
+        while (iter.hasNext())
+        {
+            if (shouldInterrupt()) return;
+            InstallerListener il = (InstallerListener) iter.next();
+            switch (action)
+            {
+            case InstallerListener.BEFORE_FILE:
+                il.beforeFile((File) firstParam, (PackFile) secondParam);
+                break;
+            case InstallerListener.AFTER_FILE:
+                il.afterFile((File) firstParam, (PackFile) secondParam);
+                break;
+            case InstallerListener.BEFORE_DIR:
+                il.beforeDir((File) firstParam, (PackFile) secondParam);
+                break;
+            case InstallerListener.AFTER_DIR:
+                il.afterDir((File) firstParam, (PackFile) secondParam);
+                break;
+            case InstallerListener.BEFORE_PACK:
+                il.beforePack((Pack) firstParam, (Integer) secondParam,
+                        (AbstractUIProgressHandler) thirdParam);
+                break;
+            case InstallerListener.AFTER_PACK:
+                il.afterPack((Pack) firstParam, (Integer) secondParam,
+                        (AbstractUIProgressHandler) thirdParam);
+                break;
+            case InstallerListener.BEFORE_PACKS:
+                il.beforePacks((AutomatedInstallData) firstParam, (Integer) secondParam,
+                        (AbstractUIProgressHandler) thirdParam);
+                break;
+            case InstallerListener.AFTER_PACKS:
+                il.afterPacks((AutomatedInstallData) firstParam,
+                        (AbstractUIProgressHandler) secondParam);
+                break;
+
+            }
+        }
+    }
+
+    /**
+     * Returns the defined custom actions split into types including a constructed type for the file
+     * related installer listeners.
+     * 
+     * @return array of lists of custom action data like listeners
+     */
+    private List[] getCustomActions()
+    {
+        String[] listenerNames = AutomatedInstallData.CUSTOM_ACTION_TYPES;
+        List[] retval = new List[listenerNames.length + 1];
+        int i;
+        for (i = 0; i < listenerNames.length; ++i)
+        {
+            retval[i] = (List) idata.customData.get(listenerNames[i]);
+            if (retval[i] == null)
+            // Make a dummy list, then iterator is ever callable.
+                retval[i] = new ArrayList();
+        }
+        if (retval[AutomatedInstallData.INSTALLER_LISTENER_INDEX].size() > 0)
+        { // Installer listeners exist
+            // Create file related installer listener list in the last
+            // element of custom action array.
+            i = retval.length - 1; // Should be so, but safe is safe ...
+            retval[i] = new ArrayList();
+            Iterator iter = ((List) retval[AutomatedInstallData.INSTALLER_LISTENER_INDEX])
+                    .iterator();
+            while (iter.hasNext())
+            {
+                // If we get a class cast exception many is wrong and
+                // we must fix it.
+                InstallerListener li = (InstallerListener) iter.next();
+                if (li.isFileListener()) retval[i].add(li);
+            }
+
+        }
+        return (retval);
+    }
+
+    /**
+     * Adds additional unistall data to the uninstall data object.
+     * 
+     * @param udata unistall data
+     * @param customData array of lists of custom action data like uninstaller listeners
+     */
+    private void handleAdditionalUninstallData(UninstallData udata, List[] customData)
+    {
+        // Handle uninstall libs
+        udata.addAdditionalData("__uninstallLibs__",
+                customData[AutomatedInstallData.UNINSTALLER_LIBS_INDEX]);
+        // Handle uninstaller listeners
+        udata.addAdditionalData("uninstallerListeners",
+                customData[AutomatedInstallData.UNINSTALLER_LISTENER_INDEX]);
+        // Handle uninstaller jars
+        udata.addAdditionalData("uninstallerJars",
+                customData[AutomatedInstallData.UNINSTALLER_JARS_INDEX]);
+    }
+
+    // This method is only used if a file related custom action exist.
+    /**
+     * Creates the given directory recursive and calls the method "afterDir" of each listener with
+     * the current file object and the pack file object. On error an exception is raised.
+     * 
+     * @param dest the directory which should be created
+     * @param pf current pack file object
+     * @param customActions all defined custom actions
+     * @return false on error, true else
+     * @throws Exception
+     */
+
+    private boolean mkDirsWithEnhancement(File dest, PackFile pf, List[] customActions)
+            throws Exception
+    {
+        String path = "unknown";
+        if (dest != null) path = dest.getAbsolutePath();
+        if (dest != null && !dest.exists() && dest.getParentFile() != null)
+        {
+            if (dest.getParentFile().exists())
+                informListeners(customActions, InstallerListener.BEFORE_DIR, dest, pf, null);
+            if (!dest.mkdir())
+            {
+                mkDirsWithEnhancement(dest.getParentFile(), pf, customActions);
+                if (!dest.mkdir()) dest = null;
+            }
+            informListeners(customActions, InstallerListener.AFTER_DIR, dest, pf, null);
+        }
+        if (dest == null)
+        {
+            handler.emitError("Error creating directories", "Could not create directory\n" + path);
+            handler.stopAction();
+            return (false);
+        }
+        return (true);
+    }
+
+    // CUSTOM ACTION STUFF -------------- end -----------------
+
+    /**
+     * Returns whether an interrupt request should be discarded or not.
+     * 
+     * @return Returns the discard interrupt flag
+     */
+    public static synchronized boolean isDiscardInterrupt()
+    {
+        return discardInterrupt;
+    }
+
+    /**
+     * Sets the discard interrupt flag.
+     * 
+     * @param di the discard interrupt flag to set
+     */
+    public static synchronized void setDiscardInterrupt(boolean di)
+    {
+        discardInterrupt = di;
+        setInterruptDesired(false);
+    }
+
+    /**
+     * Returns the interrupt desired state.
+     * 
+     * @return the interrupt desired state
+     */
+    public static boolean isInterruptDesired()
+    {
+        return interruptDesired;
+    }
+
+    /**
+     * @param interruptDesired The interrupt desired flag to set
+     */
+    private static void setInterruptDesired(boolean interruptDesired)
+    {
+        MultiVolumeUnpacker.interruptDesired = interruptDesired;
+    }
+}
\ No newline at end of file

Added: izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningInputStream.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningInputStream.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningInputStream.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,301 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+import com.izforge.izpack.util.Debug;
+
+/**
+ * An inputstream which transparently spans over multiple volumes. The amount of volumes has to be
+ * specified
+ * 
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ */
+public class FileSpanningInputStream extends InputStream
+{
+
+    private static final int EOF = -1;
+
+    protected FileInputStream fileinputstream;
+
+    protected String volumename;
+
+    protected int currentvolumeindex;
+
+    protected int volumestotal;
+
+    protected static boolean nextvolumenotfound = false;
+
+    protected long filepointer;
+
+    protected GZIPInputStream zippedinputstream;
+
+    public FileSpanningInputStream(File volume, int volumestotal) throws IOException
+    {
+        fileinputstream = new FileInputStream(volume);
+        zippedinputstream = new GZIPInputStream(fileinputstream);
+        currentvolumeindex = 0;
+        volumename = volume.getAbsolutePath();
+        this.volumestotal = volumestotal;
+        filepointer = 0;
+        Debug.trace("Opening stream to " + volume);
+    }
+
+    public FileSpanningInputStream(String volumename, int volumestotal) throws IOException
+    {
+        this(new File(volumename), volumestotal);
+    }
+
+    /**
+     * creates an inputstream to the next volume
+     * 
+     * @return true - an inputstream to the next volume has been created false - the last volume was
+     * reached
+     * @throws IOException
+     */
+    private boolean createInputStreamToNextVolume() throws IOException
+    {
+        currentvolumeindex++;
+        // have we reached the last volume?
+        if (currentvolumeindex >= volumestotal)
+        {
+            Debug.error("last volume reached.");
+            return false;
+        }
+        // the next volume name
+        String nextvolumename = volumename + "." + currentvolumeindex;
+        Debug.trace("Trying to use next volume: " + nextvolumename);
+        File nextvolumefile = new File(nextvolumename);
+        if (!nextvolumefile.exists())
+        {
+            currentvolumeindex--;
+            nextvolumenotfound = true;
+            Debug.trace("volume not found");
+            throw new VolumeNotFoundException(nextvolumename + "was not found.", nextvolumename);
+        }
+        Debug.trace("next volume found.");
+        // try to open new stream to next volume
+        fileinputstream = new FileInputStream(nextvolumefile);
+        zippedinputstream = new GZIPInputStream(fileinputstream);
+        // everything fine
+        nextvolumenotfound = false;
+        return true;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.InputStream#available()
+     */
+    public int available() throws IOException
+    {
+        if (nextvolumenotfound)
+        {
+            createInputStreamToNextVolume();
+        }
+        // return fileinputstream.available();
+        return zippedinputstream.available();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.InputStream#close()
+     */
+    public void close() throws IOException
+    {
+        zippedinputstream.close();
+        fileinputstream.close();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.InputStream#read()
+     */
+    public int read() throws IOException
+    {
+        if (nextvolumenotfound)
+        {
+            // the next volume was not found, so try to create a new input stream to next volume
+            createInputStreamToNextVolume();
+        }
+        int nextbyte = zippedinputstream.read();
+        filepointer++;
+        if (nextbyte == EOF)
+        {
+            // if end of file is reached, try to open InputStream to next volume
+            // close the inputstream
+            try
+            {
+                zippedinputstream.close();
+            }
+            catch (Exception e)
+            {
+                // do nothing
+            }
+
+            if (createInputStreamToNextVolume())
+            {
+                // try to read next byte
+                nextbyte = zippedinputstream.read();
+                filepointer++;
+            }
+        }
+        return nextbyte;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.InputStream#read(byte[], int, int)
+     */
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        if (nextvolumenotfound)
+        {
+            // the next volume was not found, so try to create a new input stream to next volume
+            createInputStreamToNextVolume();
+        }
+        int bytesread = zippedinputstream.read(b, off, len);
+        filepointer += bytesread;
+        if (bytesread == EOF)
+        {
+            filepointer++; // bytesread was -1;
+            System.out.println("EOF reached.");
+            // close the inputstream
+            try
+            {
+                zippedinputstream.close();
+            }
+            catch (Exception e)
+            {
+                // do nothing
+            }
+            // try to open next volume
+            if (createInputStreamToNextVolume())
+            {
+                // try to read next bytes
+                Debug.trace("next volume opened, continuing read");
+                bytesread = zippedinputstream.read(b, off, len);
+                filepointer += bytesread;
+                // System.out.println("read into buffer: " + bytesread + " Bytes");
+            }
+        }
+        // System.out.println("return from read into buffer: " + bytesread + " Bytes");
+        return bytesread;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.InputStream#read(byte[])
+     */
+    public int read(byte[] b) throws IOException
+    {
+        return this.read(b, 0, b.length);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.InputStream#skip(long)
+     */
+    public long skip(long n) throws IOException
+    {
+        if (nextvolumenotfound)
+        {
+            // the next volume was not found, so try to create a new input stream to next volume
+            createInputStreamToNextVolume();
+        }
+        long bytesskipped = 0;
+        byte[] buffer = new byte[4096];
+        try
+        {
+            while (bytesskipped < n)
+            {
+                int maxBytes = (int) Math.min(n - bytesskipped, buffer.length);
+
+                int bytesInBuffer = this.read(buffer, 0, maxBytes);
+                if (bytesInBuffer == -1)
+                    throw new IOException("Unexpected end of stream (installer corrupted?)");
+
+                bytesskipped += bytesInBuffer;
+            }
+        }
+        catch (VolumeNotFoundException vnfe)
+        {
+            vnfe.setAlreadyskippedbytes(bytesskipped);
+            throw vnfe;
+        }
+        return bytesskipped;
+    }
+
+    /**
+     * Returns the name of the volume
+     * 
+     * @return the name of the volume
+     */
+    public String getVolumename()
+    {
+        return volumename;
+    }
+
+    /**
+     * Sets the volumename
+     * 
+     * @param volumename
+     */
+    public void setVolumename(String volumename)
+    {
+        Debug.trace("new volumename: " + volumename);
+        // try to get the volumename from the given volume file
+        // the first volume has no suffix, additional volumes have a .INDEX# suffix
+        String volumesuffix = "." + currentvolumeindex;
+        String nextvolumesuffix = "." + (currentvolumeindex + 1);
+        if (volumename.endsWith(volumesuffix))
+        {
+            this.volumename = volumename.substring(0, volumename.lastIndexOf(volumesuffix));
+        }
+        else if (volumename.endsWith(nextvolumesuffix))
+        {
+            this.volumename = volumename.substring(0, volumename.lastIndexOf(nextvolumesuffix));
+        }
+        else
+        {
+            this.volumename = volumename;
+        }
+        Debug.trace("Set volumename to: " + this.volumename);
+    }
+
+    /**
+     * Returns the current position in the file. Notice: this is the global position in all volumes.
+     * 
+     * @return the current position in file.
+     */
+    public long getFilepointer()
+    {
+        return filepointer;
+    }
+
+}

Added: izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningOutputStream.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningOutputStream.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/io/FileSpanningOutputStream.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,353 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.io;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.GZIPOutputStream;
+
+import com.izforge.izpack.util.Debug;
+
+/**
+ * An outputstream which transparently spans over multiple volumes. The size of the volumes and an
+ * additonal space for the first volume can be specified.
+ * 
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ */
+public class FileSpanningOutputStream extends OutputStream
+{
+
+    public static final long KB = 1024;
+
+    public static final long MB = 1024 * KB;
+
+    // the default size of a volume
+    public static final long DEFAULT_VOLUME_SIZE = 650 * MB;
+
+    // free space on first volume
+    // may be used for placing additional files on cd beside the pack files
+    // default is 0, so there's no additional space
+    public static final long DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE = 0;
+
+    // the default volume name
+    protected static final String DEFAULT_VOLUME_NAME = "installer";
+
+    protected static final long FILE_NOT_AVAILABLE = -1;
+
+    // the maximum size of a volume
+    protected long maxvolumesize = DEFAULT_VOLUME_SIZE;
+
+    // the addition free space of volume 0
+    protected long firstvolumefreespacesize = DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE;
+    
+    public static final String VOLUMES_INFO = "/volumes.info";  
+
+    // the current file this stream writes to
+    protected File currentfile;
+
+    // the name of the volumes
+    protected String volumename;
+
+    // the current index of the volume, the stream writes to
+    protected int currentvolumeindex;
+
+    // a normal file outputstream for writting to the current volume
+    private FileOutputStream fileoutputstream;
+
+    private GZIPOutputStream zippedoutputstream;
+
+    // the current position in the open file
+    protected long filepointer;
+
+    protected long totalbytesofpreviousvolumes;
+
+    /**
+     * Creates a new spanning output stream with specified volume names and a maximum volume size
+     * 
+     * @param volumename - the name of the volumes
+     * @param maxvolumesize - the maximum volume size
+     * @throws IOException
+     */
+    public FileSpanningOutputStream(String volumename, long maxvolumesize) throws IOException
+    {
+        this(new File(volumename), maxvolumesize);
+    }
+
+    /**
+     * Creates a new spanning output stream with specified volume names and a maximum volume size
+     * 
+     * @param volume - the first volume
+     * @param maxvolumesize - the maximum volume size
+     * @throws IOException
+     */
+    public FileSpanningOutputStream(File volume, long maxvolumesize) throws IOException
+    {
+        this(volume, maxvolumesize, 0);
+    }
+
+    /**
+     * Creates a new spanning output stream with specified volume names and a maximum volume size
+     * 
+     * @param volume - the first volume
+     * @param maxvolumesize - the maximum volume size
+     * @param currentvolume - the current volume
+     * @throws IOException
+     */
+    protected FileSpanningOutputStream(File volume, long maxvolumesize, int currentvolume)
+            throws IOException
+    {
+        this.createVolumeOutputStream(volume, maxvolumesize, currentvolume);
+    }
+
+    /**
+     * Actually creates the outputstream for writing a volume with index currentvolume and a maximum
+     * of maxvolumesize
+     * 
+     * @param volume - the volume to write to
+     * @param maxvolumesize - the maximum volume size
+     * @param currentvolume - the currentvolume index
+     * @throws IOException
+     */
+    private void createVolumeOutputStream(File volume, long maxvolumesize, int currentvolume)
+            throws IOException
+    {
+        fileoutputstream = new FileOutputStream(volume);
+        zippedoutputstream = new GZIPOutputStream(fileoutputstream, 256);
+        currentfile = volume;
+        this.currentvolumeindex = currentvolume;
+        this.maxvolumesize = maxvolumesize;
+        // try to get the volumename from the given volume file
+        // the first volume has no suffix, additional volumes have a .INDEX# suffix
+        String volumesuffix = "." + currentvolume;
+        String volabsolutePath = volume.getAbsolutePath();
+        if (volabsolutePath.endsWith(volumesuffix))
+        {
+            volumename = volabsolutePath.substring(0, volabsolutePath.indexOf(volumesuffix));
+        }
+        else
+        {
+            volumename = volabsolutePath;
+        }
+    }
+
+    /**
+     * 
+     * @param volume
+     * @throws IOException
+     */
+    public FileSpanningOutputStream(File volume) throws IOException
+    {
+        this(volume.getAbsolutePath(), DEFAULT_VOLUME_SIZE);
+    }
+
+    /**
+     * 
+     * @param volumename
+     * @throws IOException
+     */
+    public FileSpanningOutputStream(String volumename) throws IOException
+    {
+        this(volumename, DEFAULT_VOLUME_SIZE);
+    }
+
+    /**
+     * 
+     * @throws IOException
+     */
+    public FileSpanningOutputStream() throws IOException
+    {
+        this(DEFAULT_VOLUME_NAME, DEFAULT_VOLUME_SIZE);
+    }
+
+    /**
+     * Returns the size of the current volume
+     * 
+     * @return the size of the current volume FILE_NOT_AVAILABLE, if there's no current volume
+     */
+    protected long getCurrentVolumeSize()
+    {
+        if (currentfile == null) { return FILE_NOT_AVAILABLE; }
+        try
+        {
+            flush();
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        // create a new instance
+        currentfile = new File(currentfile.getAbsolutePath());
+        if (currentvolumeindex == 0)
+        {
+            // this is the first volume, add the additional free space
+            // and add a reserve for overhead and not yet written data
+            return currentfile.length() + this.firstvolumefreespacesize
+                    + Math.round(0.001 * currentfile.length());
+        }
+        else
+        {
+            // not the first volume, just return the actual length
+            // and add a reserve for overhead and not yet written data
+            return currentfile.length() + Math.round(0.001 * currentfile.length());
+        }
+    }
+
+    /**
+     * Closes the stream to the current volume and reopens to the next volume
+     * 
+     * @throws IOException
+     */
+    protected void createStreamToNextVolume() throws IOException
+    {
+        // close current stream
+        close();
+        totalbytesofpreviousvolumes = currentfile.length();
+        currentvolumeindex++;
+        // get the name of the next volume
+        String nextvolumename = volumename + "." + currentvolumeindex;
+        // does the creation
+        this.createVolumeOutputStream(new File(nextvolumename), this.maxvolumesize,
+                this.currentvolumeindex);
+    }
+
+    /**
+     * @see java.io.OutputStream#close()
+     */
+    public void close() throws IOException
+    {
+        this.flush();
+        zippedoutputstream.close();
+        fileoutputstream.close();
+        // reset the filepointer
+        // filepointer = 0;
+    }
+
+    /**
+     * @see java.io.OutputStream#write(byte[], int, int)
+     */
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        if (len > maxvolumesize) { throw new IOException(
+                "file can't be written. buffer length exceeded maxvolumesize (" + " > "
+                        + maxvolumesize + ")"); }
+        // get the current size of this file
+        long currentsize = getCurrentVolumeSize();
+        // calculate the available bytes
+        long available = maxvolumesize - currentsize;
+
+        if (available < len)
+        {
+            Debug.trace("Not enough space left on volume. available: " + available);
+            Debug.trace("current size is: " + currentsize);
+            // there's not enough space available
+            // create the next volume
+            this.createStreamToNextVolume();
+        }
+        // enough space available, just write to the outputstream
+        zippedoutputstream.write(b, off, len);
+        // increase filepointer by written bytes
+        filepointer += len;
+    }
+
+    /**
+     * @see java.io.OutputStream#write(byte[])
+     */
+    public void write(byte[] b) throws IOException
+    {
+        this.write(b, 0, b.length);
+    }
+
+    /**
+     * @see java.io.OutputStream#write(int)
+     */
+    public void write(int b) throws IOException
+    {
+        long availablebytes = maxvolumesize - getCurrentVolumeSize();
+        if (availablebytes >= 1)
+        {
+            zippedoutputstream.write(b);
+            // increase filepointer by written byte
+            filepointer++;
+        }
+        else
+        {
+            // create next volume
+            this.createStreamToNextVolume();
+            zippedoutputstream.write(b);
+            // increase filepointer by written byte
+            filepointer++;
+        }
+    }
+
+    /**
+     * @see java.io.OutputStream#flush()
+     */
+    public void flush() throws IOException
+    {
+        zippedoutputstream.flush();
+        fileoutputstream.flush();
+    }
+
+    /**
+     * Returns the amount of currently created volumes
+     * 
+     * @return the amount of created volumes
+     */
+    public int getVolumeCount()
+    {
+        return this.currentvolumeindex + 1;
+    }
+
+    /**
+     * 
+     * @return
+     */
+    public long getFirstvolumefreespacesize()
+    {
+        return firstvolumefreespacesize;
+    }
+
+    /**
+     * 
+     * @param firstvolumefreespacesize
+     */
+    public void setFirstvolumefreespacesize(long firstvolumefreespacesize)
+    {
+        this.firstvolumefreespacesize = firstvolumefreespacesize;
+    }
+
+    /**
+     * Returns the current position in this file
+     * 
+     * @return the position in this file
+     * @throws IOException
+     */
+    public long getCompressedFilepointer() throws IOException
+    {
+        this.flush();
+        // return filepointer;
+        return totalbytesofpreviousvolumes + currentfile.length();
+    }
+
+    public long getFilepointer()
+    {
+        return filepointer;
+    }
+}

Added: izpack-src/trunk/src/lib/com/izforge/izpack/io/VolumeNotFoundException.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/io/VolumeNotFoundException.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/io/VolumeNotFoundException.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,76 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.io;
+
+import java.io.IOException;
+
+/**
+ * Exception, indicating, that a volume was not found.
+ * 
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ */
+public class VolumeNotFoundException extends IOException
+{
+
+    protected String volumename;
+
+    protected long alreadyskippedbytes;
+
+    private static final long serialVersionUID = 9062182895972373707L;
+
+    public VolumeNotFoundException()
+    {
+        super();
+    }
+
+    public VolumeNotFoundException(String message, String volumename)
+    {
+        super(message);
+        this.volumename = volumename;
+    }
+
+    /**
+     * Returns the name of the volume, which couldn't be found
+     * 
+     * @return the name of the volume
+     */
+    public String getVolumename()
+    {
+        return volumename;
+    }
+
+    /**
+     * Returns the amount of skipped bytes, if a skip-operation was in progress
+     * 
+     * @return the amount of skipped bytes
+     */
+    public long getAlreadyskippedbytes()
+    {
+        return alreadyskippedbytes;
+    }
+
+    /**
+     * Sets the amount of already skipped bytes.
+     * 
+     * @param alreadyskippedbytes
+     */
+    public void setAlreadyskippedbytes(long alreadyskippedbytes)
+    {
+        this.alreadyskippedbytes = alreadyskippedbytes;
+    }
+}
\ No newline at end of file

Added: izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaDialog.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaDialog.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaDialog.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,197 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.panels;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.HeadlessException;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import com.izforge.izpack.LocaleDatabase;
+import com.izforge.izpack.gui.ButtonFactory;
+import com.izforge.izpack.gui.IconsDatabase;
+import com.izforge.izpack.gui.LabelFactory;
+import com.izforge.izpack.installer.AutomatedInstallData;
+import com.izforge.izpack.installer.InstallerFrame;
+
+
+/**
+ * Dialog for choosing the next volume.
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ */
+public class NextMediaDialog extends JDialog implements ActionListener{
+  private static final String NEXTMEDIA_MSG_ID = "nextmedia.msg";
+  private static final String NEXTMEDIA_TITLE_ID = "nextmedia.title";
+  private static final String BROWSEBTN_ID = "nextmedia.browsebtn";
+  private static final String OKBTN_ID = "nextmedia.okbtn";
+  private static final String CANCELBTN_ID = "nextmedia.cancelbtn";
+  
+  
+  private static final long serialVersionUID = -2551719029962051020L;
+  
+  protected JLabel msg;
+  protected JTextField path;
+  protected JButton browsebtn;
+  protected JButton okbtn;
+  protected JButton cancelbtn;
+    
+  protected String nextmedianame;
+  protected String nextmediapath;
+  protected String nextmediainput;
+  protected LocaleDatabase langpack;
+  protected IconsDatabase icons;
+  
+  protected Frame owner;
+  /**
+   * @throws HeadlessException
+   */
+  public NextMediaDialog(InstallerFrame main, String nextmedia) throws HeadlessException {
+    this(null,main,nextmedia);    
+  }
+
+  /**
+   * @param owner
+   * @throws HeadlessException
+   */
+  public NextMediaDialog(Frame owner,InstallerFrame main, String nextmedia) throws HeadlessException {
+    this(owner,main.langpack,main.icons,nextmedia);    
+  }
+  
+  public NextMediaDialog(Frame owner, LocaleDatabase languagepack, IconsDatabase icons, String nextmedia) throws HeadlessException {
+    super(owner,languagepack.getString(NEXTMEDIA_TITLE_ID),true);
+    this.owner = owner;
+    this.langpack = languagepack;
+    this.icons = icons;
+    this.nextmediapath = nextmedia;
+    File nextmediafile = new File(this.nextmediapath);    
+    this.nextmedianame = nextmediafile.getName();
+    this.initUI();
+  }
+  
+  public NextMediaDialog(Frame owner, AutomatedInstallData idata, String nextmedia) throws HeadlessException {
+    this(owner,idata.langpack,null,nextmedia);            
+  }
+  
+  protected void initUI() {
+    if (this.icons != null) {
+      this.msg = LabelFactory.create(this.langpack.getString(NEXTMEDIA_MSG_ID), this.icons.getImageIcon("warning"), JLabel.LEFT);
+      this.browsebtn = ButtonFactory.createButton(this.langpack.getString(BROWSEBTN_ID), this.icons.getImageIcon("open"), new Color(230, 230, 230));
+      this.okbtn = ButtonFactory.createButton(this.langpack.getString(OKBTN_ID), this.icons.getImageIcon("ok"), new Color(230, 230, 230));      
+      this.cancelbtn = ButtonFactory.createButton(this.langpack.getString(CANCELBTN_ID), this.icons.getImageIcon("cancel"), new Color(230, 230, 230));      
+    }
+    else {
+      this.msg = new JLabel(this.langpack.getString(NEXTMEDIA_MSG_ID),JLabel.LEFT);
+      this.browsebtn = new JButton(this.langpack.getString(BROWSEBTN_ID));
+      this.okbtn = new JButton(this.langpack.getString(OKBTN_ID));
+      this.cancelbtn = new JButton(this.langpack.getString(CANCELBTN_ID));      
+    }
+    this.path = new JTextField(this.nextmediapath); 
+    this.path.setColumns(40);
+    
+    this.browsebtn.addActionListener(this);
+    this.okbtn.addActionListener(this);
+    this.cancelbtn.addActionListener(this);
+    
+    JPanel mainpanel = new JPanel();
+    mainpanel.setLayout(new BoxLayout(mainpanel,BoxLayout.PAGE_AXIS));
+    mainpanel.add(this.msg);
+    
+    JPanel pathpanel = new JPanel();
+    pathpanel.setLayout(new BoxLayout(pathpanel,BoxLayout.LINE_AXIS));
+    pathpanel.add(this.path);
+    pathpanel.add(this.browsebtn);
+    pathpanel.add(Box.createHorizontalGlue());
+    mainpanel.add(pathpanel);
+    
+    JPanel okpanel = new JPanel();
+    okpanel.setLayout(new BoxLayout(okpanel,BoxLayout.LINE_AXIS));
+    okpanel.add(Box.createHorizontalGlue());
+    okpanel.add(this.okbtn);
+    okpanel.add(this.cancelbtn);
+    okpanel.add(Box.createHorizontalGlue());
+    mainpanel.add(okpanel);
+    mainpanel.add(Box.createVerticalGlue());
+    
+    this.getContentPane().setLayout(new BorderLayout());
+    this.getContentPane().add(mainpanel,BorderLayout.CENTER);        
+    
+    this.pack();
+    // set location    
+    if (this.owner != null) {
+      Dimension mysize = this.getSize();
+      Dimension ownersize = this.owner.getSize();
+      Point position = this.owner.getLocationOnScreen();
+      Point centerposition = new Point();
+      centerposition.setLocation(position.getX()+ 0.5 * ownersize.getWidth(),position.getY() + 0.5 * ownersize.getHeight());
+      Point myposition = new Point();
+      myposition.setLocation(centerposition.getX() - 0.5 * mysize.getWidth(), centerposition.getY() - 0.5 * mysize.getHeight());
+      this.setLocation(myposition);
+    }
+  }
+  
+  public String getNextMedia() {    
+    return this.nextmediainput;
+  }
+  
+  public void actionPerformed(ActionEvent e) {
+    if (e.getSource() == this.browsebtn){           
+      JFileChooser jfc;      
+      if (this.path.getText() != null){
+         jfc = new JFileChooser(this.path.getText());
+      }
+      else {
+        jfc = new JFileChooser();
+      }
+      jfc.setFileFilter(new NextMediaFileFilter(this.nextmedianame, this.langpack));
+      jfc.setDialogTitle(this.langpack.getString("nextmedia.choosertitle"));
+      jfc.setDialogType(JFileChooser.OPEN_DIALOG);
+      jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+      if (jfc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){
+        this.nextmediainput = jfc.getSelectedFile().getAbsolutePath();
+        this.path.setText(this.nextmediainput);       
+      }
+    }
+    else if (e.getSource() == this.okbtn) {
+      this.nextmediainput = this.path.getText();
+      // close this dialog
+      this.setVisible(false);
+    }
+    else if (e.getSource() == this.cancelbtn) {
+      int option = JOptionPane.showConfirmDialog(this, this.langpack.getString("installer.quit.message") , this.langpack.getString("installer.quit.title"), JOptionPane.YES_NO_OPTION);
+      if (option == JOptionPane.YES_OPTION){
+        // exit 
+        System.exit(-1);
+      }
+    }
+  }
+}
\ No newline at end of file

Added: izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaFileFilter.java
===================================================================
--- izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaFileFilter.java	2007-01-09 11:17:58 UTC (rev 1694)
+++ izpack-src/trunk/src/lib/com/izforge/izpack/panels/NextMediaFileFilter.java	2007-01-09 11:35:48 UTC (rev 1695)
@@ -0,0 +1,61 @@
+/*
+ * IzPack - Copyright 2001-2006 Julien Ponge, All Rights Reserved.
+ * 
+ * http://www.izforge.com/izpack/ http://developer.berlios.de/projects/izpack/
+ * 
+ * Copyright 2007 Dennis Reil
+ * 
+ * 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.
+ */
+package com.izforge.izpack.panels;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+import com.izforge.izpack.LocaleDatabase;
+
+/**
+ * @author Dennis Reil, <Dennis.Reil at reddot.de>
+ *
+ */
+public class NextMediaFileFilter extends FileFilter {
+  protected String volumename;
+  protected LocaleDatabase langpack;
+  
+  public NextMediaFileFilter(String volumename, LocaleDatabase langpack) {
+    this.volumename = volumename;
+    this.langpack = langpack;
+  }
+  
+  /* (non-Javadoc)
+   * @see javax.swing.filechooser.FileFilter#accept(java.io.File)
+   */
+  public boolean accept(File f) {
+    if (f.isDirectory()) {
+      return true;
+    }
+    String filepath = f.getAbsolutePath();
+    if (filepath.endsWith(this.volumename)) {
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+  /* (non-Javadoc)
+   * @see javax.swing.filechooser.FileFilter#getDescription()
+   */
+  public String getDescription() {
+    return this.langpack.getString("nextmedia.filedesc");
+  }
+}




More information about the izpack-changes mailing list