/**
   * Unpacks a pack file.
   *
   * @param file the pack file meta-data
   * @param packInputStream the pack input stream
   * @param target the target
   * @throws IOException for any I/O error
   * @throws InstallerException for any installer exception
   */
  @Override
  public void unpack(PackFile file, ObjectInputStream packInputStream, File target)
      throws IOException, InstallerException {
    // Old way of doing the job by using the (absolute) sourcepath.
    // Since this is very likely to fail and does not conform to the documentation prefer using
    // relative
    // path's
    // pis = new FileInputStream(pf.sourcePath);

    File resolvedFile = new File(sourceDir, file.getRelativeSourcePath());
    if (!resolvedFile.exists()) {
      // try alternative destination - the current working directory
      // user.dir is likely (depends on launcher type) the current directory of the executable or
      // jar-file...
      final File userDir = new File(System.getProperty("user.dir"));
      resolvedFile = new File(userDir, file.getRelativeSourcePath());
    }
    if (resolvedFile.exists()) {
      InputStream stream = new FileInputStream(resolvedFile);
      // may have a different length & last modified than we had at compile time, therefore we have
      // to
      // build a new PackFile for the copy process...
      file =
          new PackFile(
              resolvedFile.getParentFile(),
              resolvedFile,
              file.getTargetPath(),
              file.osConstraints(),
              file.override(),
              file.overrideRenameTo(),
              file.blockable(),
              file.getAdditionals());

      copy(file, stream, target);
    } else {
      // file not found. Since this file was loosely bundled, continue with the installation.
      logger.warning("Could not find loosely bundled file: " + file.getRelativeSourcePath());
      if (prompt.confirm(
              Prompt.Type.WARNING,
              "File not found",
              "Could not find loosely bundled file: " + file.getRelativeSourcePath(),
              Prompt.Options.OK_CANCEL)
          == Prompt.Option.OK) {
        throw new InstallerException("Installation cancelled");
      }
    }
  }
  /** Write Packs to primary jar or each to a separate jar. */
  protected void writePacks() throws Exception {
    final int num = packsList.size();
    sendMsg("Writing " + num + " Pack" + (num > 1 ? "s" : "") + " into installer");

    // Map to remember pack number and bytes offsets of back references
    Map<File, Object[]> storedFiles = new HashMap<File, Object[]>();

    // Pack200 files map
    Map<Integer, File> pack200Map = new HashMap<Integer, File>();
    int pack200Counter = 0;

    // Force UTF-8 encoding in order to have proper ZipEntry names.
    primaryJarStream.setEncoding("utf-8");

    // First write the serialized files and file metadata data for each pack
    // while counting bytes.

    int packNumber = 0;
    IXMLElement root = new XMLElementImpl("packs");

    for (PackInfo packInfo : packsList) {
      Pack pack = packInfo.getPack();
      pack.nbytes = 0;
      if ((pack.id == null) || (pack.id.length() == 0)) {
        pack.id = pack.name;
      }

      // create a pack specific jar if required
      // REFACTOR : Repare web installer
      // REFACTOR : Use a mergeManager for each packages that will be added to the main merger

      //            if (packJarsSeparate) {
      // See installer.Unpacker#getPackAsStream for the counterpart
      //                String name = baseFile.getName() + ".pack-" + pack.id + ".jar";
      //                packStream = IoHelper.getJarOutputStream(name, baseFile.getParentFile());
      //            }

      sendMsg("Writing Pack " + packNumber + ": " + pack.name, PackagerListener.MSG_VERBOSE);

      // Retrieve the correct output stream
      org.apache.tools.zip.ZipEntry entry =
          new org.apache.tools.zip.ZipEntry(RESOURCES_PATH + "packs/pack-" + pack.id);
      primaryJarStream.putNextEntry(entry);
      primaryJarStream.flush(); // flush before we start counting

      ByteCountingOutputStream dos = new ByteCountingOutputStream(outputStream);
      ObjectOutputStream objOut = new ObjectOutputStream(dos);

      // We write the actual pack files
      objOut.writeInt(packInfo.getPackFiles().size());

      for (PackFile packFile : packInfo.getPackFiles()) {
        boolean addFile = !pack.loose;
        boolean pack200 = false;
        File file = packInfo.getFile(packFile);

        if (file.getName().toLowerCase().endsWith(".jar")
            && info.isPack200Compression()
            && isNotSignedJar(file)) {
          packFile.setPack200Jar(true);
          pack200 = true;
        }

        // use a back reference if file was in previous pack, and in
        // same jar
        Object[] info = storedFiles.get(file);
        if (info != null && !packJarsSeparate) {
          packFile.setPreviousPackFileRef((String) info[0], (Long) info[1]);
          addFile = false;
        }

        objOut.writeObject(packFile); // base info

        if (addFile && !packFile.isDirectory()) {
          long pos = dos.getByteCount(); // get the position

          if (pack200) {
            /*
             * Warning!
             *
             * Pack200 archives must be stored in separated streams, as the Pack200 unpacker
             * reads the entire stream...
             *
             * See http://java.sun.com/javase/6/docs/api/java/util/jar/Pack200.Unpacker.html
             */
            pack200Map.put(pack200Counter, file);
            objOut.writeInt(pack200Counter);
            pack200Counter = pack200Counter + 1;
          } else {
            FileInputStream inStream = new FileInputStream(file);
            long bytesWritten = IoHelper.copyStream(inStream, objOut);
            inStream.close();
            if (bytesWritten != packFile.length()) {
              throw new IOException("File size mismatch when reading " + file);
            }
          }

          storedFiles.put(file, new Object[] {pack.id, pos});
        }

        // even if not written, it counts towards pack size
        pack.nbytes += packFile.size();
      }

      // Write out information about parsable files
      objOut.writeInt(packInfo.getParsables().size());

      for (ParsableFile parsableFile : packInfo.getParsables()) {
        objOut.writeObject(parsableFile);
      }

      // Write out information about executable files
      objOut.writeInt(packInfo.getExecutables().size());
      for (ExecutableFile executableFile : packInfo.getExecutables()) {
        objOut.writeObject(executableFile);
      }

      // Write out information about updatecheck files
      objOut.writeInt(packInfo.getUpdateChecks().size());
      for (UpdateCheck updateCheck : packInfo.getUpdateChecks()) {
        objOut.writeObject(updateCheck);
      }

      // Cleanup
      objOut.flush();
      if (!compressor.useStandardCompression()) {
        outputStream.close();
      }

      primaryJarStream.closeEntry();

      // close pack specific jar if required
      if (packJarsSeparate) {
        primaryJarStream.closeAlways();
      }

      IXMLElement child = new XMLElementImpl("pack", root);
      child.setAttribute("nbytes", Long.toString(pack.nbytes));
      child.setAttribute("name", pack.name);
      if (pack.id != null) {
        child.setAttribute("id", pack.id);
      }
      root.addChild(child);

      packNumber++;
    }

    // Now that we know sizes, write pack metadata to primary jar.
    primaryJarStream.putNextEntry(new org.apache.tools.zip.ZipEntry(RESOURCES_PATH + "packs.info"));
    ObjectOutputStream out = new ObjectOutputStream(primaryJarStream);
    out.writeInt(packsList.size());

    for (PackInfo packInfo : packsList) {
      out.writeObject(packInfo.getPack());
    }
    out.flush();
    primaryJarStream.closeEntry();

    // Pack200 files
    Pack200.Packer packer = createAgressivePack200Packer();
    for (Integer key : pack200Map.keySet()) {
      File file = pack200Map.get(key);
      primaryJarStream.putNextEntry(
          new org.apache.tools.zip.ZipEntry(RESOURCES_PATH + "packs/pack200-" + key));
      JarFile jar = new JarFile(file);
      packer.pack(jar, primaryJarStream);
      jar.close();
      primaryJarStream.closeEntry();
    }
  }