/*
 ************************************************************************
 *
 * Copyright (c) 2003-2004, C&C Research Laboratories, NEC Europe Ltd.
 *
 * Copyright in this software belongs to C&C Research Laboratories,
 * Rathausallee 10, 53757 Sankt Augustin, Germany.
 *
 * This software may not be used, sold, licensed, transferred, copied
 * or reproduced in whole or in part in any manner or form or in or
 * on any media by any person other than in accordance with the terms
 * of the Licence Agreement supplied with the software, or otherwise
 * without the prior written consent of the copyright owners.
 *
 * This software is distributed WITHOUT ANY WARRANTY, without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE, except where stated in the Licence Agreement supplied with
 * the software.
 *
 *  Created By :           G.A. Kohring
 *  Created for Project :  GEMSS (IST-2001-37153)
 *
 ************************************************************************
 */

package de.nece.ccrle.plugins.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;
import eu.gemss.components.util.ZipArchiver;


/**
 * Implements the <code>ZipArchiver</code> utility for creating Zip archives.
 *
 * @author Greg Kohring
 */
public class ZipArchiverImp implements ZipArchiver {

    private File archiveFile = null;
    private ZipOutputStream zos = null;
    private String canonicalPath = null;

    /**
     * Creates a new archive with the specified filename. If a file with 
     * the specified name exists it will be overwritten, otherwise it will
     * be created.
     *
     * @throws IOException if an I/O Error occurs.
     * @throws FileNotFoundException if the filename points to a directory
     *      instead of a file.
     */
    public ZipArchiverImp( String archiveFilename ) 
            throws IOException, FileNotFoundException {
        if ( archiveFilename != null ) {
            this.archiveFile = new File( archiveFilename );
        }
        init();
    }

    /**
     * Creates a new archive with the specified file. 
     * If the specified
     * file exists it will be overwritten, otherwise it will
     * be created.
     *
     * @throws IOException if an I/O Error occurs.
     * @throws FileNotFoundException if the pathname points to a directory
     *      instead of a file, or if the file cannot be opened or created
     *      for any reason.
     */
    public ZipArchiverImp( File archiveFile ) 
            throws IOException, FileNotFoundException {
        if ( archiveFile != null ) {
            this.archiveFile = archiveFile;
        }
        init();
    }

    // Initialize the Archiver
    private void init() throws IOException, FileNotFoundException{
        canonicalPath = archiveFile.getCanonicalPath();
        zos = new ZipOutputStream( new FileOutputStream( archiveFile ) );
    }

    public String getName() {
        return archiveFile.getName();
    }

    /**
     * Inserts the specified file into this archive. 
     * If the <code>File</code> object points to a
     * directory, then the directory will be transversed recursively
     * adding its contents to the archive.
     *
     * @param file a <code>File</code> to be placed in the archive.
     *      Any files in the archive with the same name 
     *      will be overwritten.
     * @throws IOException if an I/O error occurs.
     * @throws ZipException if any Zip file error occurs.
     */
    public void insert( File file ) throws IOException, ZipException {
        insert( file, false );
    }

    /**
     * Inserts the specified file into this archive. 
     * If the <code>File</code> object points to a
     * directory, then the directory will be transversed recursively
     * adding its contents to the archive.
     *
     * @param file a <code>File</code> to be placed in the archive.
     *      Any files in the archive with the same name 
     *      will be overwritten.
     * @param flatten if <code>true</code> then any leading path information
     *      will be stripped from the file name before it is inserted into
     *      the archive. The default is false.
     * @throws IOException if an I/O error occurs.
     * @throws ZipException if any Zip file error occurs.
     */
    public void insert( File file, boolean flatten ) 
                    throws IOException, ZipException {

        if ( file == null ){
            throw new IOException( "no files to zip!" );
        }

        if ( file.isDirectory() ){
            addDirectory( zos, file, flatten );
        } else {
            addFile( zos, file, flatten );
        }
    }

    /**
     * Inserts the specified files into this archive. 
     *      If any of any of the <code>File</code> objects point to
     *      directories, then the directories will be transversed recursively
     *      adding their contents to the archive.
     *
     * @param files an array of <code>File</code>s to be placed in the archive.
     *      Any files in the archive with the same name 
     *      will be overwritten.
     * @throws IOException if an I/O error occurs.
     * @throws ZipException if any Zip file error occurs.
     */
    public void insert(  File[] files ) throws IOException, ZipException {
        insert( files, false );
    }

    /**
     * Inserts the specified files into this archive. 
     *      If any of any of the <code>File</code> objects point to
     *      directories, then the directories will be transversed recursively
     *      adding their contents to the archive.
     *
     * @param files an array of <code>File</code>s to be placed in the archive.
     *      Any files in the archive with the same name 
     *      will be overwritten.
     * @param flatten if <code>true</code> then any leading path information
     *      will be stripped from the file name before it is inserted into
     *      the archive. The default is false.
     * @throws IOException if an I/O error occurs.
     * @throws ZipException if any Zip file error occurs.
     */
    public void insert( File[] files, boolean flatten ) throws IOException,
        ZipException {

        // Check whether there is any work to be done
        if ( files.length == 0 ){
            throw new IOException( "no files to zip!" );
        }

        // Recursively zip all files.
        for ( int f = 0; f < files.length; f++ ){
            insert( files[f], flatten );
        }
    }

    // Adds the specified file to the specified zip output stream
    private void addFile( ZipOutputStream zos, File file, boolean flatten ) 
               throws IOException, ZipException {

        final int LENGTH = 512;
        byte[] buf = new byte[LENGTH];

        //Don't archive the archive file

        if ( file.getCanonicalPath().equals( canonicalPath ) ){
            return;
        }

        //Create the input stream for the file

        BufferedInputStream bis = new BufferedInputStream( 
                                        new FileInputStream( file ) );

        //Create the ZipEntry

        zos.putNextEntry( createEntry( file, flatten ) );

        int numBytes = 0;
        while( ( numBytes = bis.read( buf, 0, LENGTH ) ) != -1 ) {
            zos.write( buf, 0, numBytes );
        }

        zos.closeEntry();

        bis.close();
    }

    //Create the ZipEntry

    private ZipEntry createEntry( File file, boolean flatten ){

        String entryName = null;

        if ( !flatten ){

            entryName = file.getPath();

            // If we have DOS style filename, we need to remove any drive letter
            // from the entry name.

            if ( entryName.charAt( 1 ) == ':' ){
                entryName = entryName.substring( 2 );
            }

            // Remove any leading relative path redirects.

            while( entryName.startsWith( ".." ) ){
                entryName = entryName.substring( 2 );
            }

            // Remove any leading slashes

            while( entryName.charAt( 0 ) == File.separatorChar ){
                entryName = entryName.substring( 1 );
            }

            // Replace back slashes with forward slashes

            entryName = entryName.replace( '\\', '/' );

            // Add a slash onto the end of directory names.

            if ( file.isDirectory() ){
                entryName = entryName + '/';
            }

        } else {

            entryName = file.getName();

        }

        // Create the Zip Entry

        ZipEntry zipEntry = new ZipEntry( entryName );

        zipEntry.setTime( file.lastModified() );

        return zipEntry;
    }

    // Recursively zip the specified directory to the specified zip output 
    // stream
    private void addDirectory( ZipOutputStream zos, File dir, boolean flatten ) 
               throws IOException, ZipException {

        if ( !flatten ){
            zos.putNextEntry( createEntry( dir, flatten ) );
            zos.closeEntry();
        }

        File[] files = dir.listFiles();

        for ( int f = 0; f < files.length; f++ ){
            if ( files[f].isDirectory() ){
                addDirectory( zos, files[f], flatten );
            } else {
                addFile( zos, files[f], flatten );
            }
        }

    }

    /**
     * Ensures that the close method on this archive is called when there are
     * no more references to it.  It is better to call {@link #close()}
     * explicitly, since there is no guarantee the finalize method will ever be 
     * called.
     *
     * @throws Throwable if an error occurs while calling this method.
     */

    protected void finalize() throws Throwable {
        if ( zos != null ){
            close();
        }
        super.finalize();
    }

    /**
     * Close the archive. Once the archive is closed it cannot be changed.
     */
    public void close() throws IOException {
        //zos.finish();
        zos.close();
        zos = null;
    }
}
