Package org.codehaus.plexus.archiver.zip

Source Code of org.codehaus.plexus.archiver.zip.AbstractZipArchiver

package org.codehaus.plexus.archiver.zip;

/**
*
* Copyright 2004 The Apache Software Foundation
*
*  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.
*/

import org.codehaus.plexus.archiver.AbstractArchiver;
import org.codehaus.plexus.archiver.ArchiveEntry;
import org.codehaus.plexus.archiver.ArchiveFilterException;
import org.codehaus.plexus.archiver.ArchiveFinalizer;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.FilterEnabled;
import org.codehaus.plexus.archiver.FinalizerEnabled;
import org.codehaus.plexus.archiver.UnixStat;
import org.codehaus.plexus.archiver.util.FilterSupport;
import org.codehaus.plexus.util.FileUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.zip.CRC32;

/**
* @version $Revision: 3651 $ $Date: 2006-08-21 17:05:02 -0400 (Mon, 21 Aug 2006) $
*/
public abstract class AbstractZipArchiver
    extends AbstractArchiver
    implements FilterEnabled, FinalizerEnabled
{

    private String comment;

    /**
     * Encoding to use for filenames, defaults to the platform's
     * default encoding.
     */
    private String encoding;

    private boolean doCompress = true;

    private boolean doUpdate = false;

    // shadow of the above if the value is altered in execute
    private boolean savedDoUpdate = false;

    protected String archiveType = "zip";

    /*
     * Whether the original compression of entries coming from a ZIP
     * archive should be kept (for example when updating an archive).
     */
    //not used: private boolean keepCompression = false;

    private boolean doFilesonly = false;

    protected Hashtable entries = new Hashtable();

    protected String duplicate = "add";

    protected Hashtable addedDirs = new Hashtable();

    private Vector addedFiles = new Vector();

    private static final long EMPTY_CRC = new CRC32().getValue();

    protected boolean doubleFilePass = false;

    protected boolean skipWriting = false;

    /**
     * true when we are adding new files into the Zip file, as opposed
     * to adding back the unchanged files
     */
    protected boolean addingNewFiles = false;

    /**
     * Whether the file modification times will be rounded up to the
     * next even number of seconds.
     */
    private boolean roundUp = true;
   
    private FilterSupport filterSupport;

    private List finalizers;
   
    public void setArchiveFilters( List filters )
    {
        filterSupport = new FilterSupport( filters, getLogger() );
    }

    public String getComment()
    {
        return comment;
    }

    public void setComment( String comment )
    {
        this.comment = comment;
    }

    public String getEncoding()
    {
        return encoding;
    }

    public void setEncoding( String encoding )
    {
        this.encoding = encoding;
    }

    public void setCompress( boolean compress )
    {
        this.doCompress = compress;
    }

    public boolean isCompress()
    {
        return doCompress;
    }

    public void setUpdateMode( boolean update )
    {
        this.doUpdate = update;
        savedDoUpdate = doUpdate;
    }

    public boolean isInUpdateMode()
    {
        return doUpdate;
    }

    /**
     * A 3 digit octal string, specify the user, group and
     * other modes in the standard Unix fashion;
     * optional, default=0644
     *
     * @deprecated use AbstractArchiver.setDefaultFileMode(int) instead.
     */
    public void setFileMode( String octalString )
    {
        setDefaultFileMode( Integer.parseInt( octalString, 8 ) );
    }

    /**
     * @deprecated use AbstractArchiver.getDefaultFileMode() instead.
     */
    public int getFileMode()
    {
        return getDefaultFileMode();
    }

    /**
     * A 3 digit octal string, specify the user, group and
     * other modes in the standard Unix fashion;
     * optional, default=0755
     *
     * @deprecated use AbstractArchiver.setDefaultDirectoryMode(int).
     */
    public void setDirMode( String octalString )
    {
        setDefaultDirectoryMode( Integer.parseInt( octalString, 8 ) );
    }

    /**
     * @deprecated use AbstractArchiver.getDefaultDirectoryMode() instead.
     */
    public int getDirMode()
    {
        return getDefaultDirectoryMode();
    }

    /**
     * If true, emulate Sun's jar utility by not adding parent directories;
     * optional, defaults to false.
     */
    public void setFilesonly( boolean f )
    {
        doFilesonly = f;
    }

    public boolean isFilesonly()
    {
        return doFilesonly;
    }

    /**
     * Whether the file modification times will be rounded up to the
     * next even number of seconds.
     * <p/>
     * <p>Zip archives store file modification times with a
     * granularity of two seconds, so the times will either be rounded
     * up or down.  If you round down, the archive will always seem
     * out-of-date when you rerun the task, so the default is to round
     * up.  Rounding up may lead to a different type of problems like
     * JSPs inside a web archive that seem to be slightly more recent
     * than precompiled pages, rendering precompilation useless.</p>
     */
    public void setRoundUp( boolean r )
    {
        roundUp = r;
    }

    public boolean isRoundUp()
    {
        return roundUp;
    }

    public void createArchive()
        throws ArchiverException, IOException
    {
        if ( ! checkForced() )
        {
            return;
        }

        if ( doubleFilePass )
        {
            skipWriting = true;
            createArchiveMain();
            skipWriting = false;
            createArchiveMain();
        }
        else
        {
            createArchiveMain();
        }
    }

    private void createArchiveMain()
        throws ArchiverException
    {
        Map archiveEntries = getFiles();

        if ( archiveEntries == null || archiveEntries.size() == 0 )
        {
            new ArchiverException( "You must set at least one file." );
        }

        File zipFile = getDestFile();

        if ( zipFile == null )
        {
            new ArchiverException( "You must set the destination " + archiveType + "file." );
        }

        if ( zipFile.exists() && !zipFile.isFile() )
        {
            new ArchiverException( zipFile + " isn't a file." );
        }

        if ( zipFile.exists() && !zipFile.canWrite() )
        {
            new ArchiverException( zipFile + " is read-only." );
        }

        // Renamed version of original file, if it exists
        File renamedFile = null;
        // Whether or not an actual update is required -
        // we don't need to update if the original file doesn't exist

        addingNewFiles = true;

        if ( doUpdate && !zipFile.exists() )
        {
            doUpdate = false;
            getLogger().debug( "ignoring update attribute as " + archiveType + " doesn't exist." );
        }

        boolean success = false;

        try
        {
            if ( doUpdate )
            {
                renamedFile =
                    FileUtils.createTempFile( "zip", ".tmp",
                                              zipFile.getParentFile() );
                renamedFile.deleteOnExit();

                try
                {
                    FileUtils.rename( zipFile, renamedFile );
                }
                catch ( SecurityException e )
                {
                    getLogger().debug( e.toString() );
                    throw new ArchiverException(
                        "Not allowed to rename old file ("
                        + zipFile.getAbsolutePath()
                        + ") to temporary file", e );
                }
                catch ( IOException e )
                {
                    getLogger().debug( e.toString() );
                    throw new ArchiverException(
                        "Unable to rename old file ("
                        + zipFile.getAbsolutePath()
                        + ") to temporary file", e );
                }
            }

            String action = doUpdate ? "Updating " : "Building ";

            getLogger().info( action + archiveType + ": " + zipFile.getAbsolutePath() );

            ZipOutputStream zOut = null;
            try
            {
                if ( !skipWriting )
                {
                    zOut = new ZipOutputStream( zipFile );

                    zOut.setEncoding( encoding );
                    if ( doCompress )
                    {
                        zOut.setMethod( ZipOutputStream.DEFLATED );
                    }
                    else
                    {
                        zOut.setMethod( ZipOutputStream.STORED );
                    }
                }
                initZipOutputStream( zOut );

                // Add the new files to the archive.
                addResources( getResourcesToAdd( zipFile ), zOut );

                if ( doUpdate )
                {
                    addResources( getResourcesToUpdate( zipFile ), zOut );
                }
                finalizeZipOutputStream( zOut );

                // If we've been successful on an update, delete the
                // temporary file
                if ( doUpdate )
                {
                    if ( !renamedFile.delete() )
                    {
                        getLogger().warn( "Warning: unable to delete temporary file "
                                          + renamedFile.getName() );
                    }
                }
                success = true;
            }
            finally
            {
                // Close the output stream.
                try
                {
                    if ( zOut != null )
                    {
                        zOut.close();
                    }
                }
                catch ( IOException ex )
                {
                    // If we're in this finally clause because of an
                    // exception, we don't really care if there's an
                    // exception when closing the stream. E.g. if it
                    // throws "ZIP file must have at least one entry",
                    // because an exception happened before we added
                    // any files, then we must swallow this
                    // exception. Otherwise, the error that's reported
                    // will be the close() error, which is not the
                    // real cause of the problem.
                    if ( success )
                    {
                        throw ex;
                    }
                }
            }
        }
        catch ( IOException ioe )
        {
            String msg = "Problem creating " + archiveType + ": "
                         + ioe.getMessage();

            // delete a bogus ZIP file (but only if it's not the original one)
            if ( ( !doUpdate || renamedFile != null ) && !zipFile.delete() )
            {
                msg += " (and the archive is probably corrupt but I could not "
                       + "delete it)";
            }

            if ( doUpdate && renamedFile != null )
            {
                try
                {
                    FileUtils.rename( renamedFile, zipFile );
                }
                catch ( IOException e )
                {
                    msg += " (and I couldn't rename the temporary file "
                           + renamedFile.getName() + " back)";
                }
            }

            throw new ArchiverException( msg, ioe );
        }
        finally
        {
            cleanUp();
        }
    }

    protected Map getResourcesToAdd( File file )
        throws IOException
    {
        if ( !file.exists() || !doUpdate )
        {
            return getFiles();
        }

        ZipFile zipFile = new ZipFile( file );

        Map result = new HashMap();

        for ( Iterator iter = getFiles().keySet().iterator(); iter.hasNext(); )
        {
            String fileName = (String) iter.next();
            if ( zipFile.getEntry( fileName ) == null )
            {
                result.put( fileName, getFiles().get( fileName ) );
            }
        }
        return result;
    }

    protected Map getResourcesToUpdate( File file )
        throws IOException
    {
        Map result = new HashMap();

        if ( file.exists() && doUpdate )
        {
            ZipFile zipFile = new ZipFile( file );
            for ( Iterator iter = getFiles().keySet().iterator(); iter.hasNext(); )
            {
                String fileName = (String) iter.next();
                ZipEntry zipEntry;

                if ( ( zipEntry = zipFile.getEntry( fileName ) ) != null )
                {
                    ArchiveEntry currentEntry = (ArchiveEntry) getFiles().get( fileName );
                    if ( zipEntry.getTime() < currentEntry.getFile().lastModified() )
                    {
                        result.put( fileName, getFiles().get( fileName ) );
                    }
                }
            }
        }

        return result;
    }

    /**
     * Add the given resources.
     *
     * @param resources the resources to add
     * @param zOut      the stream to write to
     */
    protected final void addResources( Map resources, ZipOutputStream zOut )
        throws IOException, ArchiverException
    {
        File base = null;

        for ( Iterator iter = resources.keySet().iterator(); iter.hasNext(); )
        {
            String name = (String) iter.next();
            ArchiveEntry entry = (ArchiveEntry) resources.get( name );
            name = name.replace( File.separatorChar, '/' );

            if ( "".equals( name ) )
            {
                continue;
            }

            if ( entry.getFile().isDirectory() && !name.endsWith( "/" ) )
            {
                name = name + "/";
            }

            addParentDirs( base, name, zOut, "" );

            if ( entry.getFile().isFile() )
            {
                zipFile( entry, zOut, name );
            }
            else
            {
                zipDir( entry.getFile(), zOut, name, entry.getMode() );
            }
        }
    }

    /**
     * Ensure all parent dirs of a given entry have been added.
     */
    protected final void addParentDirs( File baseDir, String entry,
                                        ZipOutputStream zOut, String prefix )
        throws IOException
    {
        if ( !doFilesonly && getIncludeEmptyDirs() )
        {
            Stack directories = new Stack();

            // Don't include the last entry itself if it's
            // a dir; it will be added on its own.
            int slashPos = entry.length() - ( entry.endsWith( "/" ) ? 1 : 0 );

            while ( ( slashPos = entry.lastIndexOf( '/', slashPos - 1 ) ) != -1 )
            {
                String dir = entry.substring( 0, slashPos + 1 );

                if ( addedDirs.contains( prefix + dir ) )
                {
                    break;
                }

                directories.push( dir );
            }

            while ( !directories.isEmpty() )
            {
                String dir = (String) directories.pop();
                File f;
                if ( baseDir != null )
                {
                    f = new File( baseDir, dir );
                }
                else
                {
                    f = new File( dir );
                }
                zipDir( f, zOut, prefix + dir, getDefaultDirectoryMode() );
            }
        }
    }

    /**
     * Adds a new entry to the archive, takes care of duplicates as well.
     *
     * @param in           the stream to read data for the entry from.
     * @param zOut         the stream to write to.
     * @param vPath        the name this entry shall have in the archive.
     * @param lastModified last modification time for the entry.
     * @param fromArchive  the original archive we are copying this
     *                     entry from, will be null if we are not copying from an archive.
     * @param mode         the Unix permissions to set.
     */
    protected void zipFile( InputStream in, ZipOutputStream zOut, String vPath,
                            long lastModified, File fromArchive, int mode )
        throws IOException, ArchiverException
    {
        try
        {
            if ( filterSupport != null && !filterSupport.include( in, vPath ) )
            {
                return;
            }
        }
        catch ( ArchiveFilterException e )
        {
            throw new ArchiverException( "Error verifying \'" + vPath + "\' for inclusion: " + e.getMessage(), e );
        }
       
        if ( entries.contains( vPath ) )
        {
            if ( duplicate.equals( "preserve" ) )
            {
                getLogger().info( vPath + " already added, skipping" );
                return;
            }
            else if ( duplicate.equals( "fail" ) )
            {
                throw new ArchiverException( "Duplicate file " + vPath
                                             + " was found and the duplicate "
                                             + "attribute is 'fail'." );
            }
            else
            {
                // duplicate equal to add, so we continue
                getLogger().debug( "duplicate file " + vPath
                                   + " found, adding." );
            }
        }
        else
        {
            getLogger().debug( "adding entry " + vPath );
        }

        entries.put( vPath, vPath );

        if ( !skipWriting )
        {
            ZipEntry ze = new ZipEntry( vPath );
            ze.setTime( lastModified );
            ze.setMethod( doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED );

            /*
            * ZipOutputStream.putNextEntry expects the ZipEntry to
            * know its size and the CRC sum before you start writing
            * the data when using STORED mode - unless it is seekable.
            *
            * This forces us to process the data twice.
            */
            if ( !zOut.isSeekable() && !doCompress )
            {
                long size = 0;
                CRC32 cal = new CRC32();
                if ( !in.markSupported() )
                {
                    // Store data into a byte[]
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();

                    byte[] buffer = new byte[8 * 1024];
                    int count = 0;

                    do
                    {
                        size += count;
                        cal.update( buffer, 0, count );
                        bos.write( buffer, 0, count );
                        count = in.read( buffer, 0, buffer.length );
                    }
                    while ( count != -1 );

                    in = new ByteArrayInputStream( bos.toByteArray() );
                }
                else
                {
                    in.mark( Integer.MAX_VALUE );
                    byte[] buffer = new byte[8 * 1024];
                    int count = 0;

                    do
                    {
                        size += count;
                        cal.update( buffer, 0, count );
                        count = in.read( buffer, 0, buffer.length );
                    }
                    while ( count != -1 );

                    in.reset();
                }
                ze.setSize( size );
                ze.setCrc( cal.getValue() );
            }

            ze.setUnixMode( UnixStat.FILE_FLAG | mode );
            zOut.putNextEntry( ze );

            byte[] buffer = new byte[8 * 1024];
            int count = 0;
            do
            {
                if ( count != 0 )
                {
                    zOut.write( buffer, 0, count );
                }
                count = in.read( buffer, 0, buffer.length );
            }
            while ( count != -1 );
        }

        addedFiles.addElement( vPath );
    }

    /**
     * Method that gets called when adding from java.io.File instances.
     * <p/>
     * <p>This implementation delegates to the six-arg version.</p>
     *
     * @param entry the file to add to the archive
     * @param zOut  the stream to write to
     * @param vPath the name this entry shall have in the archive
     */
    protected void zipFile( ArchiveEntry entry, ZipOutputStream zOut, String vPath )
        throws IOException, ArchiverException
    {
        if ( entry.equals( getDestFile() ) )
        {
            throw new ArchiverException( "A zip file cannot include itself" );
        }

        FileInputStream fIn = new FileInputStream( entry.getFile() );
        try
        {
            // ZIPs store time with a granularity of 2 seconds, round up
            zipFile( fIn, zOut, vPath, entry.getFile().lastModified() + ( roundUp ? 1999 : 0 ), null, entry.getMode() );
        }
        finally
        {
            fIn.close();
        }
    }

    /**
     *
     */
    protected void zipDir( File dir, ZipOutputStream zOut, String vPath,
                           int mode )
        throws IOException
    {
        if ( addedDirs.get( vPath ) != null )
        {
            // don't add directories we've already added.
            // no warning if we try, it is harmless in and of itself
            return;
        }

        getLogger().debug( "adding directory " + vPath );
        addedDirs.put( vPath, vPath );

        if ( !skipWriting )
        {
            ZipEntry ze = new ZipEntry( vPath );

            if ( dir != null && dir.exists() )
            {
                // ZIPs store time with a granularity of 2 seconds, round up
                ze.setTime( dir.lastModified() + ( roundUp ? 1999 : 0 ) );
            }
            else
            {
                // ZIPs store time with a granularity of 2 seconds, round up
                ze.setTime( System.currentTimeMillis() + ( roundUp ? 1999 : 0 ) );
            }
            ze.setSize( 0 );
            ze.setMethod( ZipEntry.STORED );
            // This is faintly ridiculous:
            ze.setCrc( EMPTY_CRC );
            ze.setUnixMode( mode );

            zOut.putNextEntry( ze );
        }
    }

    /**
     * Create an empty zip file
     *
     * @return true for historic reasons
     */
    protected boolean createEmptyZip( File zipFile )
        throws ArchiverException
    {
        // In this case using java.util.zip will not work
        // because it does not permit a zero-entry archive.
        // Must create it manually.
        getLogger().info( "Note: creating empty " + archiveType + " archive " + zipFile );
        OutputStream os = null;
        try
        {
            os = new FileOutputStream( zipFile );
            // Cf. PKZIP specification.
            byte[] empty = new byte[22];
            empty[ 0 ] = 80; // P
            empty[ 1 ] = 75; // K
            empty[ 2 ] = 5;
            empty[ 3 ] = 6;
            // remainder zeros
            os.write( empty );
        }
        catch ( IOException ioe )
        {
            throw new ArchiverException( "Could not create empty ZIP archive "
                                         + "(" + ioe.getMessage() + ")", ioe );
        }
        finally
        {
            if ( os != null )
            {
                try
                {
                    os.close();
                }
                catch ( IOException e )
                {
                    //ignore
                }
            }
        }
        return true;
    }

    /**
     * Do any clean up necessary to allow this instance to be used again.
     * <p/>
     * <p>When we get here, the Zip file has been closed and all we
     * need to do is to reset some globals.</p>
     * <p/>
     * <p>This method will only reset globals that have been changed
     * during execute(), it will not alter the attributes or nested
     * child elements.  If you want to reset the instance so that you
     * can later zip a completely different set of files, you must use
     * the reset method.</p>
     *
     * @see #reset
     */
    protected void cleanUp()
    {
        addedDirs.clear();
        addedFiles.removeAllElements();
        entries.clear();
        addingNewFiles = false;
        doUpdate = savedDoUpdate;
    }

    /**
     * Makes this instance reset all attributes to their default
     * values and forget all children.
     *
     * @see #cleanUp
     */
    public void reset()
    {
        setDestFile( null );
        duplicate = "add";
        archiveType = "zip";
        doCompress = true;
        doUpdate = false;
        doFilesonly = false;
        encoding = null;
    }

    /**
     * method for subclasses to override
     */
    protected void initZipOutputStream( ZipOutputStream zOut )
        throws IOException, ArchiverException
    {
    }

    /**
     * method for subclasses to override
     */
    protected void finalizeZipOutputStream( ZipOutputStream zOut )
        throws IOException, ArchiverException
    {
        runArchiveFinalizers();
    }
   
    public void setArchiveFinalizers( List archiveFinalizers )
    {
        this.finalizers = archiveFinalizers;
    }

    protected List getArchiveFinalizers()
    {
        return finalizers;
    }

    protected void runArchiveFinalizers()
        throws ArchiverException
    {
        if ( finalizers != null )
        {
            for ( Iterator it = finalizers.iterator(); it.hasNext(); )
            {
                ArchiveFinalizer finalizer = (ArchiveFinalizer) it.next();
               
                finalizer.finalizeArchiveCreation( this );
            }
        }
    }

    public boolean isSupportingForced() {
        return true;
    }
}
TOP

Related Classes of org.codehaus.plexus.archiver.zip.AbstractZipArchiver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.