package org.codehaus.plexus.archiver.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.codehaus.plexus.components.io.functions.FileSupplier;
import org.codehaus.plexus.components.io.resources.PlexusIoResource;
import org.codehaus.plexus.util.IOUtil;

/**
 * Utility class for work with {@link PlexusIoResource} instances.
 */
public class ResourceUtils
{
    /**
     * Private constructor, to prevent accidental implementation.
     */
    private ResourceUtils()
    {
        // Does nothing
    }

    /**
     * Queries, whether the given source is up-to-date relative to
     * the given destination.
     */
    public static boolean isUptodate( PlexusIoResource source, File destination )
    {
        return isUptodate( source, destination.lastModified() );
    }

    /**
     * Queries, whether the given source is up-to-date relative to
     * the given modification date.
     */
    public static boolean isUptodate( PlexusIoResource source, long destinationDate )
    {
        final long s = source.getLastModified();
        if ( s == PlexusIoResource.UNKNOWN_MODIFICATION_DATE )
        {
            return false;
        }

        if ( destinationDate == 0 )
        {
            return false;
        }

        return destinationDate > s;
    }

    /**
     * Queries, whether the given source is up-to-date relative to
     * the given modification date.
     */
    public static boolean isUptodate( long sourceDate, long destinationDate )
    {
        if ( sourceDate == PlexusIoResource.UNKNOWN_MODIFICATION_DATE )
        {
            return false;
        }

        if ( destinationDate == 0 )
        {
            return false;
        }

        return destinationDate > sourceDate;
    }

    /**
     * Copies the sources contents to the given destination file.
     */
    public static void copyFile( PlexusIoResource in, File outFile )
        throws IOException
    {
        InputStream input = null;
        OutputStream output = null;
        try
        {
            input = in.getContents();
            output = new FileOutputStream( outFile );
            IOUtil.copy( input, output );
        }
        finally
        {
            IOUtil.close( input );
            IOUtil.close( output );
        }
    }

    /**
     * Copies the sources contents to the given destination file.
     */
    public static void copyFile( InputStream input, File outFile )
        throws IOException
    {
        OutputStream output = null;
        try
        {
            output = new FileOutputStream( outFile );
            IOUtil.copy( input, output );
        }
        finally
        {
            IOUtil.close( input );
            IOUtil.close( output );
        }
    }

    /**
     * Checks, whether the resource and the file are identical.
     */
    public static boolean isSame( PlexusIoResource resource, File file )
    {
        if ( resource instanceof FileSupplier )
        {
            File resourceFile = ((FileSupplier) resource).getFile();
            return file.equals( resourceFile );
        }
        return false;
    }

    /**
     * Checks, whether the resource and the file are identical.
     * Uses {@link File#getCanonicalFile()} for comparison, which is much
     * slower than comparing the files.
     */
    public static boolean isCanonicalizedSame( PlexusIoResource resource, File file )
        throws IOException
    {
        if ( resource instanceof FileSupplier)
        {
            File resourceFile = ((FileSupplier) resource).getFile();
            return file.getCanonicalFile().equals( resourceFile.getCanonicalFile() );
        }
        return false;
    }
}


package org.codehaus.plexus.archiver.tar;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.FilterInputStream;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.codehaus.plexus.archiver.ArchiveFile;

import static org.codehaus.plexus.archiver.util.Streams.bufferedInputStream;

/**
 *
Implementation of {@link ArchiveFile} for tar files.

 *
Compared to
 * {@link org.apache.commons.compress.archivers.zip.ZipFile}, this one should be used with some care, due to the
 * nature of a tar file: While a zip file contains a catalog, a tar
 * file does not. In other words, the only way to read a tar file in
 * a performant manner is by iterating over it from the beginning to
 * the end. If you try to open another entry than the "next" entry,
 * then you force to skip entries, until the requested entry is found.
 * This may require to reread the entire file!

 *
In other words, the recommended use of this class is to use
 * {@link #getEntries()} and invoke {@link #getInputStream(TarArchiveEntry)}
 * only for the current entry. Basically, this is to handle it like
 * {@link TarArchiveInputStream}.

 *
The advantage of this class is that you may write code for the
 * {@link ArchiveFile}, which is valid for both tar files and zip files.

 */
public class TarFile
    implements ArchiveFile
{
    private final java.io.File file;
    private TarArchiveInputStream inputStream;
    private TarArchiveEntry currentEntry;

    /**
     * Creates a new instance with the given file.
     */
    public TarFile( File file )
    {
        this.file = file;
    }

    /**
     * Implementation of {@link ArchiveFile#getEntries()}. Note, that there is
     * an interaction between this method and {@link #getInputStream(TarArchiveEntry)},
     * or {@link #getInputStream(org.apache.commons.compress.archivers.ArchiveEntry)}:
     * If an input stream is opened for any other entry than the enumerations
     * current entry, then entries may be skipped.
     */
    public Enumeration<org.apache.commons.compress.archivers.ArchiveEntry> getEntries()
        throws IOException
    {
        if ( inputStream != null )
        {
            close();
        }
        open();
        return new Enumeration<org.apache.commons.compress.archivers.ArchiveEntry>()
        {
            boolean currentEntryValid;

            public boolean hasMoreElements()
            {
                if ( !currentEntryValid )
                {
                    try
                    {
                        currentEntry = inputStream.getNextTarEntry();
                    }
                    catch ( IOException  e )
                    {
                        throw new UndeclaredThrowableException( e );
                    }
                }
                return currentEntry != null;
            }

            public org.apache.commons.compress.archivers.ArchiveEntry nextElement()
            {
                if ( currentEntry == null )
                {
                    throw new NoSuchElementException();
                }
                currentEntryValid = false;
                return currentEntry;
            }
        };
    }

    public void close()
        throws IOException
    {
        if ( inputStream != null )
        {
            inputStream.close();
            inputStream = null;
        }
    }

    public InputStream getInputStream( org.apache.commons.compress.archivers.ArchiveEntry entry )
        throws IOException
    {
        return getInputStream(new TarArchiveEntry(entry.getName()));
    }

    /**
     * Returns an {@link InputStream} with the given entries
     * contents. This {@link InputStream} may be closed: Nothing
     * happens in that case, because an actual close would invalidate
     * the underlying {@link TarArchiveInputStream}.
     */
    public InputStream getInputStream( TarArchiveEntry entry )
        throws IOException
    {
        if ( entry.equals( (Object) currentEntry )  &&  inputStream != null )
        {
            return new FilterInputStream( inputStream )
            {
                public void close()
                    throws IOException
                {
                    // Does nothing.
                }
            };
        }
        return getInputStream( entry, currentEntry );
    }

    protected InputStream getInputStream( File file )
        throws IOException
    {
        final FileInputStream fileInputStream = new FileInputStream( file );
        return fileInputStream;
    }

    private InputStream getInputStream( TarArchiveEntry entry, TarArchiveEntry currentEntry )
        throws IOException
    {
        if ( currentEntry == null  ||  inputStream == null )
        {
            // Search for the entry from the beginning of the file to the end.
            if ( inputStream != null )
            {
                close();
            }
            open();
            if ( !findEntry( entry, null ) )
            {
                throw new IOException( "Unknown entry: " + entry.getName() );
            }
        }
        else
        {
            // Search for the entry from the current position to the end of the file.
            if ( findEntry( entry, null ) )
            {
                return getInputStream( entry );
            }
            close();
            open();
            if ( !findEntry( entry, currentEntry ) )
            {
                throw new IOException( "No such entry: " + entry.getName() );
            }
        }
        return getInputStream( entry );
    }

    private void open()
        throws IOException
    {
        inputStream = new TarArchiveInputStream( bufferedInputStream( getInputStream( file ) ), "UTF8" );
    }

    private boolean findEntry( TarArchiveEntry entry, TarArchiveEntry currentEntry)
        throws IOException
    {
        for (;;)
        {
            this.currentEntry = inputStream.getNextTarEntry();
            if ( this.currentEntry == null
                    ||  (currentEntry != null  &&  this.currentEntry.equals( currentEntry ) ) )
            {
                return false;
            }
            if ( this.currentEntry.equals( entry ) )
            {
                return true;
            }
        }
    }
}


package org.codehaus.plexus.archiver.war;

/*
 * Copyright  2000-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.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.codehaus.plexus.archiver.ArchiveEntry;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.util.ResourceUtils;

import java.io.File;
import java.io.IOException;

/**
 * An extension of <jar> to create a WAR archive.
 * Contains special treatment for files that should end up in the
 * WEB-INF/lib, WEB-INF/classes or
 * WEB-INF directories of the Web Application Archive.

 *
(The War task is a shortcut for specifying the particular layout of a WAR file.
 * The same thing can be accomplished by using the prefix and fullpath
 * attributes of zipfilesets in a Zip or Jar task.)

 *
The extended zipfileset element from the zip task
 * (with attributes prefix, fullpath, and src)
 * is available in the War task.

 *
 * @see JarArchiver
 */
public class WarArchiver
    extends JarArchiver
{

    /**
     * our web.xml deployment descriptor
     */
    private File deploymentDescriptor;

    /**
     * flag set if finding the webxml is to be expected.
     */
    private boolean expectWebXml = true;

    /**
     * flag set if the descriptor is added
     */
    private boolean descriptorAdded;

    /*
     * @deprecated Use setExpectWebXml instead !
     * @param excpectWebXml true if web xml is *expected* from the client
     */
    @Deprecated
    public void setIgnoreWebxml( boolean excpectWebXml )
    {
        expectWebXml = excpectWebXml;
    }

    /*.
     * Indicates if the client is required to supply web.xml
     * @param excpectWebXml true if web xml is *expected* from the client
     */
    public void setExpectWebXml( boolean expectWebXml )
    {
        this.expectWebXml = expectWebXml;
    }

    public WarArchiver()
    {
        super();
        archiveType = "war";
    }

    /**
     * set the deployment descriptor to use (WEB-INF/web.xml);
     * required unless <tt>update=true</tt>
     */
    public void setWebxml( File descr )
        throws ArchiverException
    {
        deploymentDescriptor = descr;
        if ( !deploymentDescriptor.exists() )
        {
            throw new ArchiverException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." );
        }

        addFile( descr, "WEB-INF" + File.separatorChar + "web.xml" );
    }

    /**
     * add a file under WEB-INF/lib/
     */

    public void addLib( File fileName )
        throws ArchiverException
    {
        addDirectory( fileName.getParentFile(), "WEB-INF/lib/", new String[]{fileName.getName()}, null );
    }

    /**
     * add files under WEB-INF/lib/
     */

    public void addLibs( File directoryName, String[] includes, String[] excludes )
        throws ArchiverException
    {
        addDirectory( directoryName, "WEB-INF/lib/", includes, excludes );
    }

    /**
     * add a file under WEB-INF/lib/
     */

    public void addClass( File fileName )
        throws ArchiverException
    {
        addDirectory( fileName.getParentFile(), "WEB-INF/classes/", new String[]{fileName.getName()}, null );
    }

    /**
     * add files under WEB-INF/classes
     */
    public void addClasses( File directoryName, String[] includes, String[] excludes )
        throws ArchiverException
    {
        addDirectory( directoryName, "WEB-INF/classes/", includes, excludes );
    }

    /**
     * files to add under WEB-INF;
     */
    public void addWebinf( File directoryName, String[] includes, String[] excludes )
        throws ArchiverException
    {
        addDirectory( directoryName, "WEB-INF/", includes, excludes );
    }

    /**
     * override of  parent; validates configuration
     * before initializing the output stream.
     */
    protected void initZipOutputStream( ZipArchiveOutputStream zOut )
        throws ArchiverException, IOException
    {
        // If no webxml file is specified, it's an error.
        if ( expectWebXml && deploymentDescriptor == null && !isInUpdateMode() )
        {
            throw new ArchiverException( "webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)" );
        }
        super.initZipOutputStream( zOut );
    }

    /**
     * Overridden from ZipArchiver class to deal with web.xml
     */
    protected void zipFile( ArchiveEntry entry, ZipArchiveOutputStream zOut, String vPath )
        throws IOException, ArchiverException
    {
        // If the file being added is WEB-INF/web.xml, we warn if it's
        // not the one specified in the "webxml" attribute - or if
        // it's being added twice, meaning the same file is specified
        // by the "webxml" attribute and in a <fileset> element.
        if ( vPath.equalsIgnoreCase( "WEB-INF/web.xml" ) )
        {
            if ( descriptorAdded || ( expectWebXml
                 && ( deploymentDescriptor == null
                     || !ResourceUtils.isCanonicalizedSame( entry.getResource(), deploymentDescriptor ) ) ) )
            {
                getLogger().warn( "Warning: selected " + archiveType
                                  + " files include a WEB-INF/web.xml which will be ignored "
                                  + "\n(webxml attribute is missing from "
                                  + archiveType + " task, or ignoreWebxml attribute is specified as 'true')" );
            }
            else
            {
                super.zipFile( entry, zOut, vPath );
                descriptorAdded = true;
            }
        }
        else
        {
            super.zipFile( entry, zOut, vPath );
        }
    }

    /**
     * Make sure we don't think we already have a web.xml next time this task
     * gets executed.
     */
    protected void cleanUp()
        throws IOException
    {
        descriptorAdded = false;
        expectWebXml = true;
        super.cleanUp();
    }
}


package org.codehaus.plexus.archiver.jar;

/**
 *
 * 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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.jar.Attributes;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.util.IOUtil;

/**
 * Holds the data of a jar manifest.
 *

 * Manifests are processed according to the
 * {@link Jar
 * file specification.}.
 * Specifically, a manifest element consists of
 * a set of attributes and sections. These sections in turn may contain
 * attributes. Note in particular that this may result in manifest lines
 * greater than 72 bytes being wrapped and continued on the next
 * line. If an application can not handle the continuation mechanism, it
 * is a defect in the application, not this task.
 *
 * @since Ant 1.4
 */
public class Manifest
    extends java.util.jar.Manifest implements Iterable<String>
{

    /**
     * The Name Attribute is the first in a named section
     */
    private static final String ATTRIBUTE_NAME = ManifestConstants.ATTRIBUTE_NAME;

    /**
     * The From Header is disallowed in a Manifest
     */
    private static final String ATTRIBUTE_FROM = ManifestConstants.ATTRIBUTE_FROM;

    /**
     * Default Manifest version if one is not specified
     */
    private static final String DEFAULT_MANIFEST_VERSION = ManifestConstants.DEFAULT_MANIFEST_VERSION;

    /**
     * The max length of a line in a Manifest
     */
    private static final int MAX_LINE_LENGTH = 72;

    /**
     * Max length of a line section which is continued. Need to allow
     * for the CRLF.
     */
    private static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2;

    /**
     * The End-Of-Line marker in manifests
     */
    static final String EOL = "\r\n";

    public static class BaseAttribute
    {
        /**
         * The attribute's name
         */
        protected String name = null;

        /**
         * Get the Attribute's name
         *
         * @return the attribute's name.
         */
        public String getName()
        {
            return name;
        }

        @Override
        public boolean equals( Object o )
        {
            if ( this == o )
            {
                return true;
            }
            if ( !( o instanceof BaseAttribute ) )
            {
                return false;
            }

            BaseAttribute that = (BaseAttribute) o;

            return !( name != null ? !name.equals( that.name ) : that.name != null );

        }

        @Override
        public int hashCode()
        {
            return name != null ? name.hashCode() : 0;
        }
    }

    /**
     * An attribute for the manifest.
     * Those attributes that are not nested into a section will be added to the "Main" section.
     */
    public static class Attribute
        extends BaseAttribute implements Iterable<String>
    {

        /**
         * The attribute's value
         */
        private Vector<String> values = new Vector<String>();

        /**
         * For multivalued attributes, this is the index of the attribute
         * currently being defined.
         */
        private int currentIndex = 0;

        /**
         * Construct an empty attribute
         */
        public Attribute()
        {
        }

        /**
         * Construct a manifest by specifying its name and value
         *
         * @param name  the attribute's name
         * @param value the Attribute's value
         */
        public Attribute( String name, String value )
        {
            this.name = name;
            setValue( value );
        }

        public Iterator<String> iterator()
        {
            return values.iterator();
        }

        /**
         * @see java.lang.Object#hashCode
         */
        public int hashCode()
        {
            int hashCode = super.hashCode();
            hashCode += values.hashCode();
            return hashCode;
        }

        /**
         * @see java.lang.Object#equals
         */
        public boolean equals( Object rhs )
        {
            if ( super.equals( rhs ) )
            {
                return false;
            }
            if ( rhs == null || rhs.getClass() != getClass() )
            {
                return false;
            }

            if ( rhs == this )
            {
                return true;
            }

            Attribute rhsAttribute = (Attribute) rhs;
            String lhsKey = getKey();
            String rhsKey = rhsAttribute.getKey();
            //noinspection SimplifiableIfStatement,ConstantConditions
            if ( ( lhsKey == null && rhsKey != null ) || ( lhsKey != null && rhsKey == null ) || !lhsKey.equals(
                rhsKey ) )
            {
                return false;
            }

            return rhsAttribute.values != null && values.equals( rhsAttribute.values );
        }

        /**
         * Set the Attribute's name; required
         *
         * @param name the attribute's name
         */
        public void setName( String name )
        {
            this.name = name;
        }

        /**
         * Get the attribute's Key - its name in lower case.
         *
         * @return the attribute's key.
         */
        public String getKey()
        {
            return getKey (name );
        }

        /**
         * Get the key for the specified attribute name - its name in lower case.
         *
         * @return the attribute's key.
         */
        private static String getKey( String name )
        {
            if ( name == null )
            {
                return null;
            }
            return name.toLowerCase( Locale.ENGLISH );
        }

        /**
         * Set the Attribute's value; required
         *
         * @param value the attribute's value
         */
        public void setValue( String value )
        {
            if ( currentIndex >= values.size() )
            {
                values.addElement( value );
                currentIndex = values.size() - 1;
            }
            else
            {
                values.setElementAt( value, currentIndex );
            }
        }

        /**
         * Get the Attribute's value.
         *
         * @return the attribute's value.
         */
        public String getValue()
        {
            if ( values.size() == 0 )
            {
                return null;
            }

            String fullValue = "";
            for ( String value : values )
            {
                fullValue += value + " ";
            }
            return fullValue.trim();
        }

        /**
         * Add a new value to this attribute - making it multivalued.
         *
         * @param value the attribute's additional value
         */
        public void addValue( String value )
        {
            currentIndex++;
            setValue( value );
        }

        /**
         * Write the attribute out to a print writer.
         *
         * @param writer the Writer to which the attribute is written
         * @throws IOException if the attribute value cannot be written
         */
        void write( PrintWriter writer )
            throws IOException
        {
            StringWriter sWriter = new StringWriter();
            PrintWriter bufferWriter = new PrintWriter( sWriter );

            for ( String value : values )
            {
                writeValue( bufferWriter, value );
            }

            byte[] convertedToUtf8 = sWriter.toString().getBytes( "UTF-8" );

            writer.print( new String( convertedToUtf8, "UTF-8" ) );
        }

        /**
         * Write a single attribute value out.  Should handle multiple lines of attribute value.
         *
         * @param writer the Writer to which the attribute is written
         * @param value  the attribute value
         * @throws IOException if the attribute value cannot be written
         */
        private void writeValue( PrintWriter writer, String value )
            throws IOException
        {
            String nameValue = name + ": " + value;

            StringTokenizer tokenizer = new StringTokenizer( nameValue, "\n\r" );

            String prefix = "";

            while ( tokenizer.hasMoreTokens() )
            {
                writeLine( writer, prefix + tokenizer.nextToken() );
                prefix = " ";
            }
        }

        /**
         * Write a single Manifest line. Should handle more than 72 characters of line
         *
         * @param writer the Writer to which the attribute is written
         * @param line   the manifest line to be written
         * @throws java.io.IOException when Io excepts
         */
        private void writeLine( PrintWriter writer, String line )
            throws IOException
        {
            while ( line.getBytes().length > MAX_LINE_LENGTH )
            {
                // try to find a MAX_LINE_LENGTH byte section
                int breakIndex = MAX_SECTION_LENGTH;
                String section = line.substring( 0, breakIndex );
                while ( section.getBytes().length > MAX_SECTION_LENGTH && breakIndex > 0 )
                {
                    breakIndex--;
                    section = line.substring( 0, breakIndex );
                }
                if ( breakIndex == 0 )
                {
                    throw new IOException( "Unable to write manifest line " + line );
                }
                writer.print( section + EOL );
                line = " " + line.substring( breakIndex );
            }
            writer.print( line + EOL );
        }
    }

    public class ExistingAttribute
        extends Attribute implements Iterable<String>
    {
        private final Attributes attributes;

        public ExistingAttribute(Attributes attributes, String name)
        {
            this.attributes = attributes;
            this.name = name;
        }

        public Iterator<String> iterator()
        {
            return getKeys(attributes).iterator();
        }

        public void setName( String name )
        {
            throw new UnsupportedOperationException( "Cant do this" );
        }

        public String getKey()
        {
            return name;
        }

        public void setValue( String value )
        {
            attributes.putValue( name, value );
        }

        public String getValue()
        {
            return attributes.getValue( name );
        }

        public void addValue( String value )
        {
            String value1 = getValue();
            value1 =  (value1 != null) ? " " + value : value;
            setValue( value1 );
        }

        void write( PrintWriter writer )
            throws IOException
        {
            throw new UnsupportedOperationException( "Cant do this" );
        }
    }

    private static Collection<String> getKeys(Attributes attributes){
        Collection<String> result = new ArrayList<String>(  );
        for ( Object objectObjectEntry : attributes.keySet() )
        {
              result.add( objectObjectEntry.toString() );
        }
        return result;
    }
    /**
     * A manifest section - you can nest attribute elements into sections.
     * A section consists of a set of attribute values,
     * separated from other sections by a blank line.
     */
    public static class Section implements Iterable<String>
    {
        /**
         * Warnings for this section
         */
        private Vector<String> warnings = new Vector<String>();

        /**
         * The section's name if any. The main section in a
         * manifest is unnamed.
         */
        private String name = null;

        /**
         * The section's attributes.
         */
        private Hashtable<String, Attribute> attributes = new Hashtable<String, Attribute>();

        /**
         * Index used to retain the attribute ordering
         */
        private Vector<String> attributeIndex = new Vector<String>();

        /**
         * The name of the section; optional -default is the main section.
         *
         * @param name the section's name
         */
        public void setName( String name )
        {
            this.name = name;
        }

        /**
         * Get the Section's name.
         *
         * @return the section's name.
         */
        public String getName()
        {
            return name;
        }

        public Iterator<String> iterator()
        {
            return attributes.keySet().iterator();
        }

        /**
         * Get a attribute of the section
         *
         * @param attributeName the name of the attribute
         * @return a Manifest.Attribute instance if the attribute is
         *         single-valued, otherwise a Vector of Manifest.Attribute
         *         instances.
         */
        public Attribute getAttribute( String attributeName )
        {
            return attributes.get( attributeName.toLowerCase() );
        }

        /**
         * Add an attribute to the section.
         *
         * @param attribute the attribute to be added to the section
         * @throws ManifestException if the attribute is not valid.
         */
        public void addConfiguredAttribute( Attribute attribute )
            throws ManifestException
        {
            String check = addAttributeAndCheck( attribute );
            if ( check != null )
            {
                throw new ManifestException(
                    "Specify the section name using " + "the \"name\" attribute of the
 element rather "
                        + "than using a \"Name\" manifest attribute" );
            }
        }

        /**
         * Add an attribute to the section
         *
         * @param attribute the attribute to be added.
         * @return the value of the attribute if it is a name
         *         attribute - null other wise
         * @throws ManifestException if the attribute already
         *                           exists in this section.
         */
        public String addAttributeAndCheck( Attribute attribute )
            throws ManifestException
        {
            if ( attribute.getName() == null || attribute.getValue() == null )
            {
                throw new ManifestException( "Attributes must have name and value" );
            }
            if ( attribute.getKey().equalsIgnoreCase( ATTRIBUTE_NAME ) )
            {
                warnings.addElement(
                    "\"" + ATTRIBUTE_NAME + "\" attributes " + "should not occur in the main section and must be the "
                        + "first element in all other sections: \"" + attribute.getName() + ": " + attribute.getValue()
                        + "\"" );
                return attribute.getValue();
            }

            if ( attribute.getKey().startsWith( Attribute.getKey( ATTRIBUTE_FROM ) ) )
            {
                warnings.addElement( "Manifest attributes should not start " + "with \"" + ATTRIBUTE_FROM + "\" in \""
                                         + attribute.getName() + ": " + attribute.getValue() + "\"" );
            }
            else
            {
                // classpath attributes go into a vector
                String attributeKey = attribute.getKey();
                if ( attributeKey.equalsIgnoreCase( ManifestConstants.ATTRIBUTE_CLASSPATH ) )
                {
                    Attribute classpathAttribute = attributes.get( attributeKey );

                    if ( classpathAttribute == null )
                    {
                        storeAttribute( attribute );
                    }
                    else
                    {
                        warnings.addElement( "Multiple Class-Path attributes " + "are supported but violate the Jar "
                                                 + "specification and may not be correctly "
                                                 + "processed in all environments" );
                        for (String value : attribute )
                        {
                            classpathAttribute.addValue( value );
                        }
                    }
                }
                else if ( attributes.containsKey( attributeKey ) )
                {
                    throw new ManifestException( "The attribute \"" + attribute.getName() + "\" may not occur more "
                                                     + "than once in the same section" );
                }
                else
                {
                    storeAttribute( attribute );
                }
            }
            return null;
        }

        /**
         * Store an attribute and update the index.
         *
         * @param attribute the attribute to be stored
         */
        protected void storeAttribute( Attribute attribute )
        {
            if ( attribute == null )
            {
                return;
            }

            String attributeKey = attribute.getKey();
            attributes.put( attributeKey, attribute );
            if ( !attributeIndex.contains( attributeKey ) )
            {
                attributeIndex.addElement( attributeKey );
            }
        }

        /**
         * Get the warnings for this section.
         *
         * @return an Enumeration of warning strings.
         */
        public Enumeration<String> getWarnings()
        {
            return warnings.elements();
        }

        /**
         * @see java.lang.Object#hashCode
         */
        public int hashCode()
        {
            int hashCode = 0;

            if ( name != null )
            {
                hashCode += name.hashCode();
            }

            hashCode += attributes.hashCode();
            return hashCode;
        }

        /**
         * @see java.lang.Object#equals
         */
        public boolean equals( Object rhs )
        {
            if ( rhs == null || rhs.getClass() != getClass() )
            {
                return false;
            }

            if ( rhs == this )
            {
                return true;
            }

            Section rhsSection = (Section) rhs;

            return rhsSection.attributes != null && attributes.equals( rhsSection.attributes );
        }
    }

    public class ExistingSection implements Iterable<String>
    {
        private final Attributes backingAttributes;

        private final String sectionName;

        public ExistingSection( Attributes backingAttributes, String sectionName )
        {
            this.backingAttributes = backingAttributes;
            this.sectionName = sectionName;
        }

        public Iterator<String> iterator()
        {
            return getKeys( backingAttributes ).iterator();
        }

        public ExistingAttribute getAttribute( String attributeName )
        {
            Attributes.Name name = new Attributes.Name( attributeName );
             return backingAttributes.containsKey( name )
                ? new ExistingAttribute( backingAttributes,attributeName )
                : null;

        }
        public String getName()
        {
            return sectionName;
        }

        public String getAttributeValue( String attributeName )
        {
            return backingAttributes.getValue( attributeName );
        }

        public void removeAttribute( String attributeName )
        {
            backingAttributes.remove( new Attributes.Name( attributeName ) );
        }

        public void addConfiguredAttribute( Attribute attribute )
            throws ManifestException
        {
            backingAttributes.putValue( attribute.getName(), attribute.getValue() );
        }

        public String addAttributeAndCheck( Attribute attribute )
            throws ManifestException
        {
            return remap( backingAttributes, attribute );
        }

        public int hashCode()
        {
            return backingAttributes.hashCode();
        }

        public boolean equals( Object rhs )
        {
            return rhs instanceof ExistingSection && backingAttributes.equals(
                ( (ExistingSection) rhs ).backingAttributes );
        }

    }

    public Iterator<String> iterator()
    {
        return getEntries().keySet().iterator();
    }

    /**
     * The main section of this manifest
     */
    private Section mainSection = new Section();

    /**
     * Construct a manifest from Ant's default manifest file.
     *
     * @return the default manifest.
     * @throws ArchiverException if there is a problem loading the
     *                           default manifest
     */
    public static Manifest getDefaultManifest()
        throws ArchiverException
    {
        try
        {
            String defManifest = "/org/codehaus/plexus/archiver/jar/defaultManifest.mf";
            InputStream in = Manifest.class.getResourceAsStream( defManifest );
            if ( in == null )
            {
                throw new ArchiverException( "Could not find default manifest: " + defManifest );
            }
            try
            {
                Manifest defaultManifest = new Manifest( new InputStreamReader( in, "UTF-8" ) );
                defaultManifest.getMainAttributes().putValue( "Created-By", System.getProperty(
                    "java.vm.version" ) + " (" + System.getProperty( "java.vm.vendor" ) + ")" );
                return defaultManifest;
            }
            catch ( UnsupportedEncodingException e )
            {
                return new Manifest( new InputStreamReader( in ) );
            }
            finally
            {
                IOUtil.close( in );
            }
        }
        catch ( ManifestException e )
        {
            throw new ArchiverException( "Default manifest is invalid !!", e );
        }
        catch ( IOException e )
        {
            throw new ArchiverException( "Unable to read default manifest", e );
        }
    }

    /**
     * Construct an empty manifest
     */
    public Manifest()
    {
        setManifestVersion();
    }

    private void setManifestVersion()
    {
        getMainAttributes().put( Attributes.Name.MANIFEST_VERSION, "1.0" );
    }

    /**
     * Read a manifest file from the given reader
     *
     * @param r is the reader from which the Manifest is read
     * @throws ManifestException if the manifest is not valid according
     *                           to the JAR spec
     * @throws IOException       if the manifest cannot be read from the reader.
     */
    public Manifest( Reader r )
        throws ManifestException, IOException
    {
        super( getInputStream( r ) );
        setManifestVersion();
    }

    public Manifest( InputStream is )
        throws IOException
    {
        super( is );
        setManifestVersion();
    }

    /**
     * Add a section to the manifest
     *
     * @param section the manifest section to be added
     * @throws ManifestException if the secti0on is not valid.
     */
    public void addConfiguredSection( Section section )
        throws ManifestException
    {
        String sectionName = section.getName();
        if ( sectionName == null )
        {
            throw new ManifestException( "Sections must have a name" );
        }
        Attributes attributes = getOrCreateAttributes( sectionName );
        for ( String s : section.attributes.keySet() )
        {

            Attribute attribute = section.getAttribute( s );
            attributes.putValue( attribute.getName(), attribute.getValue() );
        }
    }

    private Attributes getOrCreateAttributes( String name )
    {
        Attributes attributes = getAttributes( name );
        if ( attributes == null )
        {
            attributes = new Attributes();
            getEntries().put( name, attributes );
        }
        return attributes;
    }

    /**
     * Add an attribute to the manifest - it is added to the main section.
     *
     * @param attribute the attribute to be added.
     * @throws ManifestException if the attribute is not valid.
     */
    public void addConfiguredAttribute( Attribute attribute )
        throws ManifestException
    {
        remap( getMainAttributes(), attribute );
    }

    /**
     * Write the manifest out to a print writer.
     *
     * @param writer the Writer to which the manifest is written
     * @throws IOException if the manifest cannot be written
     */
    public void write( PrintWriter writer )
        throws IOException
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        super.write( byteArrayOutputStream );
        for ( byte b : byteArrayOutputStream.toByteArray() )
        {
            writer.write( (char) b );
        }
    }

    /**
     * Convert the manifest to its string representation
     *
     * @return a multiline string with the Manifest as it
     *         appears in a Manifest file.
     */
    public String toString()
    {
        StringWriter sw = new StringWriter();
        try
        {
            write( new PrintWriter( sw ) );
        }
        catch ( IOException e )
        {
            return null;
        }
        return sw.toString();
    }

    /**
     * Get the warnings for this manifest.
     *
     * @return an enumeration of warning strings
     */
    Enumeration<String> getWarnings()
    {
        Vector<String> warnings = new Vector<String>();

        Enumeration<String> warnEnum = mainSection.getWarnings();
        while ( warnEnum.hasMoreElements() )
        {
            warnings.addElement( warnEnum.nextElement() );
        }

        return warnings.elements();
    }

    /**
     * Get the version of the manifest
     *
     * @return the manifest's version string
     */
    public String getManifestVersion()
    {
        /*
      The version of this manifest
     */
        return DEFAULT_MANIFEST_VERSION;
    }

    /**
     * Get the main section of the manifest
     *
     * @return the main section of the manifest
     */
    public ExistingSection getMainSection()
    {
        return new ExistingSection( getMainAttributes(), null );
    }

    /**
     * Get a particular section from the manifest
     *
     * @param name the name of the section desired.
     * @return the specified section or null if that section
     *         does not exist in the manifest
     */
    public ExistingSection getSection( String name )
    {
        Attributes attributes = getAttributes( name );
        if ( attributes != null )
        {
            return new ExistingSection( attributes, name );
        }
        return null;
    }

    private static InputStream getInputStream( Reader r )
        throws IOException
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int read;
        while ( ( read = r.read() ) != -1 )
        {
            byteArrayOutputStream.write( read );
        }
        return new ByteArrayInputStream( byteArrayOutputStream.toByteArray() );
    }

    public static String remap( Attributes backingAttributes, Attribute attribute )
        throws ManifestException
    {
        if ( attribute.getKey() == null || attribute.getValue() == null )
        {
            throw new ManifestException( "Attributes must have name and value" );
        }

        String attributeKey = attribute.getKey();
        if ( attributeKey.equalsIgnoreCase( ManifestConstants.ATTRIBUTE_CLASSPATH ) )
        {
            String classpathAttribute = backingAttributes.getValue( attributeKey );

            if ( classpathAttribute == null )
            {
                classpathAttribute = attribute.getValue();
            }
            else
            {
                classpathAttribute += " " + attribute.getValue();
            }
            backingAttributes.putValue( ManifestConstants.ATTRIBUTE_CLASSPATH, classpathAttribute );
        }
        else
        {
            backingAttributes.putValue( attribute.getName(), attribute.getValue() );
            if ( attribute.getKey().equalsIgnoreCase( ATTRIBUTE_NAME ) )
            {
                return attribute.getValue();
            }
        }
        return null;

    }
}


package org.codehaus.plexus.archiver.tar;

/**
 *
 * 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.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.codehaus.plexus.archiver.AbstractArchiver;
import org.codehaus.plexus.archiver.ArchiveEntry;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.ResourceIterator;
import org.codehaus.plexus.archiver.util.ResourceUtils;
import org.codehaus.plexus.archiver.util.Streams;
import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
import org.codehaus.plexus.components.io.resources.PlexusIoResource;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.xerial.snappy.SnappyOutputStream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;

import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;

/**
 * @author Emmanuel Venisse
 * @version $Revision$ $Date$
 */
public class TarArchiver
    extends AbstractArchiver
{
    /**
     * Indicates whether the user has been warned about long files already.
     */
    private boolean longWarningGiven = false;

    private TarLongFileMode longFileMode = TarLongFileMode.warn;

    private TarCompressionMethod compression = TarCompressionMethod.none;

    private TarOptions options = new TarOptions();

    private TarArchiveOutputStream tOut;

    /**
     * Set how to handle long files, those with a path>100 chars.
     * Optional, default=warn.
     *

     * Allowable values are
     *

     *
  truncate - paths are truncated to the maximum length
     *
  fail - paths greater than the maximum cause a build exception
     *
  warn - paths greater than the maximum cause a warning and GNU is used
     *
  gnu - GNU extensions are used for any paths greater than the maximum.
     *
  posix - posix extensions are used for any paths greater than the maximum.
     *
  posixwarn - posix extensions are used (with warning) for any paths greater than the maximum.
     *
  omit - paths greater than the maximum are omitted from the archive
     *

     *
     * @param mode the mode to handle long file names.
     */
    public void setLongfile( TarLongFileMode mode )
    {
        this.longFileMode = mode;
    }

    /**
     * Set compression method.
     * Allowable values are
     *

     *
  none - no compression
     *
  gzip - Gzip compression
     *
  bzip2 - Bzip2 compression
     *

     *
     * @param mode the compression method.
     */
    public void setCompression( TarCompressionMethod mode )
    {
        this.compression = mode;
    }

    protected void execute()
        throws ArchiverException, IOException
    {
        if ( !checkForced() )
        {
            return;
        }

        ResourceIterator iter = getResources();
        if ( !iter.hasNext() )
        {
            throw new ArchiverException( "You must set at least one file." );
        }

        File tarFile = getDestFile();

        if ( tarFile == null )
        {
            throw new ArchiverException( "You must set the destination tar file." );
        }
        if ( tarFile.exists() && !tarFile.isFile() )
        {
            throw new ArchiverException( tarFile + " isn't a file." );
        }
        if ( tarFile.exists() && !tarFile.canWrite() )
        {
            throw new ArchiverException( tarFile + " is read-only." );
        }

        getLogger().info( "Building tar: " + tarFile.getAbsolutePath() );

        final OutputStream bufferedOutputStream = bufferedOutputStream( new FileOutputStream( tarFile ) );
        tOut =
            new TarArchiveOutputStream( compress( compression, bufferedOutputStream ), "UTF8" );
        if ( longFileMode.isTruncateMode() )
        {
            tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_TRUNCATE );
        }
        else if ( longFileMode.isPosixMode() || longFileMode.isPosixWarnMode() )
        {
            tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_POSIX );
			// Todo: Patch 2.5.1   for this fix. Also make closeable fix on 2.5.1
			tOut.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
        }
        else if ( longFileMode.isFailMode() || longFileMode.isOmitMode() )
        {
            tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_ERROR );
        }
        else
        {
            // warn or GNU
            tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_GNU );
        }

        longWarningGiven = false;
        try
        {
            while ( iter.hasNext() )
            {
                ArchiveEntry entry = iter.next();
                // Check if we don't add tar file in itself
                if ( ResourceUtils.isSame( entry.getResource(), tarFile ) )
                {
                    throw new ArchiverException( "A tar file cannot include itself." );
                }
                String fileName = entry.getName();
                String name = StringUtils.replace( fileName, File.separatorChar, '/' );

                tarFile( entry, tOut, name );
            }
        } finally
        {
            IOUtil.close( tOut );
        }
    }

    /**
     * tar a file
     *
     * @param entry the file to tar
     * @param tOut  the output stream
     * @param vPath the path name of the file to tar
     * @throws IOException on error
     */
    protected void tarFile( ArchiveEntry entry, TarArchiveOutputStream tOut, String vPath )
        throws ArchiverException, IOException
    {

        // don't add "" to the archive
        if ( vPath.length() <= 0 )
        {
            return;
        }

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

        if ( vPath.startsWith( "/" ) && !options.getPreserveLeadingSlashes() )
        {
            int l = vPath.length();
            if ( l <= 1 )
            {
                // we would end up adding "" to the archive
                return;
            }
            vPath = vPath.substring( 1, l );
        }

        int pathLength = vPath.length();
        InputStream fIn = null;

        try
        {
            TarArchiveEntry te;
			if (  !longFileMode.isGnuMode() && pathLength >= org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN )
			{
        		int maxPosixPathLen = org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN + org.apache.commons.compress.archivers.tar.TarConstants.PREFIXLEN;
            	if ( longFileMode.isPosixMode() )
            	{
            	}
            	else if ( longFileMode.isPosixWarnMode() )
            	{
            		if ( pathLength > maxPosixPathLen )
            		{
                        getLogger().warn( "Entry: " + vPath + " longer than " + maxPosixPathLen + " characters." );
                        if ( !longWarningGiven )
                        {
                            getLogger().warn( "Resulting tar file can only be processed "
                                              + "successfully by GNU compatible tar commands" );
                            longWarningGiven = true;
                        }
            		}
            	}
            	else if ( longFileMode.isOmitMode() )
                {
                    getLogger().info( "Omitting: " + vPath );
                    return;
                }
                else if ( longFileMode.isWarnMode() )
                {
                    getLogger().warn( "Entry: " + vPath + " longer than " + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN + " characters." );
                    if ( !longWarningGiven )
                    {
                        getLogger().warn( "Resulting tar file can only be processed "
                                          + "successfully by GNU compatible tar commands" );
                        longWarningGiven = true;
                    }
                }
                else if ( longFileMode.isFailMode() )
                {
                    throw new ArchiverException( "Entry: " + vPath + " longer than " + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
                                                 + " characters." );
                }
                else
                {
                    throw new IllegalStateException("Non gnu mode should never get here?");
                }
            }

            if ( entry.getType() == ArchiveEntry.SYMLINK )
            {
                final SymlinkDestinationSupplier plexusIoSymlinkResource = (SymlinkDestinationSupplier) entry.getResource();
                te = new TarArchiveEntry( vPath, TarArchiveEntry.LF_SYMLINK);
                te.setLinkName( plexusIoSymlinkResource.getSymlinkDestination() );
            }
            else
            {
                te = new TarArchiveEntry( vPath );
            }

            long teLastModified = entry.getResource().getLastModified();
            te.setModTime( teLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE ? System.currentTimeMillis()
                               : teLastModified );

            if (entry.getType() == ArchiveEntry.SYMLINK){
                te.setSize( 0 );

            } else
            if ( !entry.getResource().isDirectory()  )
            {
                final long size = entry.getResource().getSize();
                te.setSize( size == PlexusIoResource.UNKNOWN_RESOURCE_SIZE ? 0 : size );
            }
            te.setMode( entry.getMode() );

            PlexusIoResourceAttributes attributes = entry.getResourceAttributes();

            te.setUserName( ( attributes != null && attributes.getUserName() != null ) ? attributes.getUserName()
                                : options.getUserName() );
            te.setGroupName((attributes != null && attributes.getGroupName() != null) ? attributes.getGroupName()
					: options.getGroup());

            final int userId =
                ( attributes != null && attributes.getUserId() != null ) ? attributes.getUserId() : options.getUid();
            if ( userId >= 0 )
            {
                te.setUserId( userId );
            }

            final int groupId =
                ( attributes != null && attributes.getGroupId() != null ) ? attributes.getGroupId() : options.getGid();
            if ( groupId >= 0 )
            {
                te.setGroupId( groupId );
            }

            tOut.putArchiveEntry(te);

            try {
                if (entry.getResource().isFile() && !(entry.getType() == ArchiveEntry.SYMLINK)) {
                    fIn = entry.getInputStream();

                    Streams.copyFullyDontCloseOutput(fIn, tOut, "xAR");
                }

            } catch (Throwable e){
                getLogger().warn("When creating tar entry", e);
            } finally {
                tOut.closeArchiveEntry();
            }
    }
        finally
        {
            IOUtil.close( fIn );
        }
    }

    /**
     * Valid Modes for Compression attribute to Tar Task
     */
    public class TarOptions
    {
        private String userName = "";

        private String groupName = "";

        private int uid;

        private int gid;

        private boolean preserveLeadingSlashes = false;

        /**
         * The username for the tar entry
         * This is not the same as the UID.
         *
         * @param userName the user name for the tar entry.
         */
        public void setUserName( String userName )
        {
            this.userName = userName;
        }

        /**
         * @return the user name for the tar entry
         */
        public String getUserName()
        {
            return userName;
        }

        /**
         * The uid for the tar entry
         * This is not the same as the User name.
         *
         * @param uid the id of the user for the tar entry.
         */
        public void setUid( int uid )
        {
            this.uid = uid;
        }

        /**
         * @return the uid for the tar entry
         */
        public int getUid()
        {
            return uid;
        }

        /**
         * The groupname for the tar entry; optional, default=""
         * This is not the same as the GID.
         *
         * @param groupName the group name string.
         */
        public void setGroup( String groupName )
        {
            this.groupName = groupName;
        }

        /**
         * @return the group name string.
         */
        public String getGroup()
        {
            return groupName;
        }

        /**
         * The GID for the tar entry; optional, default="0"
         * This is not the same as the group name.
         *
         * @param gid the group id.
         */
        public void setGid( int gid )
        {
            this.gid = gid;
        }

        /**
         * @return the group identifier.
         */
        public int getGid()
        {
            return gid;
        }

        /**
         * @return the leading slashes flag.
         */
        public boolean getPreserveLeadingSlashes()
        {
            return preserveLeadingSlashes;
        }

        /**
         * Flag to indicates whether leading `/'s should
         * be preserved in the file names.
         * Optional, default is false.
         *
         * @param preserveLeadingSlashes the leading slashes flag.
         */
        public void setPreserveLeadingSlashes( boolean preserveLeadingSlashes )
        {
            this.preserveLeadingSlashes = preserveLeadingSlashes;
        }
    }

    /**
     * Valid Modes for Compression attribute to Tar Task
     */
    public static enum TarCompressionMethod
    {
        none, gzip, bzip2, snappy

    }

    private OutputStream compress( TarCompressionMethod tarCompressionMethod, final OutputStream ostream )
        throws IOException
    {
        if ( TarCompressionMethod.gzip.equals( tarCompressionMethod ))
        {
            return new GZIPOutputStream( ostream );
        }
        else if ( TarCompressionMethod.bzip2.equals( tarCompressionMethod) )
        {
            return new BZip2CompressorOutputStream( ostream );
        }
        else if ( TarCompressionMethod.snappy.equals( tarCompressionMethod ))
        {
            return new SnappyOutputStream( ostream );
        }
        return ostream;
    }

    public boolean isSupportingForced()
    {
        return true;
    }

    protected void cleanUp()
        throws IOException
    {
        super.cleanUp();
        tOut.close();
    }

    protected void close()
        throws IOException
    {
        IOUtil.close( tOut );
    }

    protected String getArchiveType()
    {
        return "TAR";
    }
}


package org.codehaus.plexus.archiver.dir;

/*
 * Copyright 2001-2005 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.ArchiverException;
import org.codehaus.plexus.archiver.ResourceIterator;
import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
import org.codehaus.plexus.archiver.util.ResourceUtils;
import org.codehaus.plexus.components.io.resources.PlexusIoResource;

import java.io.File;
import java.io.IOException;

/**
 * A plexus archiver implementation that stores the files to archive in a directory.
 */
public class DirectoryArchiver
    extends AbstractArchiver
{
    public void resetArchiver()
        throws IOException
    {
        cleanUp();
    }

    public void execute()
        throws ArchiverException, IOException
    {
        // Most of this method was copied from org.codehaus.plexus.archiver.tar.TarArchiver
        // and modified to store files in a directory, not a tar archive.
        final ResourceIterator iter = getResources();
        if ( !iter.hasNext() )
        {
            throw new ArchiverException( "You must set at least one file." );
        }

        final File destDirectory = getDestFile();
        if ( destDirectory == null )
        {
            throw new ArchiverException( "You must set the destination directory." );
        }
        if ( destDirectory.exists() && !destDirectory.isDirectory() )
        {
            throw new ArchiverException( destDirectory + " is not a directory." );
        }
        if ( destDirectory.exists() && !destDirectory.canWrite() )
        {
            throw new ArchiverException( destDirectory + " is not writable." );
        }

        getLogger().info( "Copying files to " + destDirectory.getAbsolutePath() );

        try
        {
            while ( iter.hasNext() )
            {
                final ArchiveEntry f = iter.next();
                // Check if we don't add directory file in itself
                if ( ResourceUtils.isSame( f.getResource(), destDirectory ) )
                {
                    throw new ArchiverException( "The destination directory cannot include itself." );
                }
                String fileName = f.getName();
                final String destDir = destDirectory.getCanonicalPath();
                fileName = destDir + File.separator + fileName;
                copyFile( f, fileName );
            }
        }
        catch ( final IOException ioe )
        {
            final String message = "Problem copying files : " + ioe.getMessage();
            throw new ArchiverException( message, ioe );
        }
    }

    /**
     * Copies the specified file to the specified path, creating any ancestor directory structure as necessary.
     *
     * @param file
     *            The file to copy (IOException will be thrown if this does not exist)
     * @param vPath
     *            The fully qualified path to copy the file to.
     * @throws ArchiverException
     *             If there is a problem creating the directory structure
     * @throws IOException
     *             If there is a problem copying the file
     */
    protected void copyFile( final ArchiveEntry entry, final String vPath )
        throws ArchiverException, IOException
    {
        // don't add "" to the archive
        if ( vPath.length() <= 0 )
        {
            return;
        }

        final PlexusIoResource in = entry.getResource();
        final File outFile = new File( vPath );

        final long inLastModified = in.getLastModified();
        final long outLastModified = outFile.lastModified();
        if ( ResourceUtils.isUptodate( inLastModified, outLastModified ) )
        {
            return;
        }

        if ( !in.isDirectory() )
        {
            if ( !outFile.getParentFile().exists() )
            {
                // create the parent directory...
                if ( !outFile.getParentFile().mkdirs() )
                {
                    // Failure, unable to create specified directory for some unknown reason.
                    throw new ArchiverException( "Unable to create directory or parent directory of " + outFile );
                }
            }
            ResourceUtils.copyFile( entry.getInputStream(), outFile );

            if ( !isIgnorePermissions() )
            {
                ArchiveEntryUtils.chmod( outFile, entry.getMode(), getLogger(), isUseJvmChmod() );
            }
        }
        else
        { // file is a directory
            if ( outFile.exists() )
            {
                if ( !outFile.isDirectory() )
                {
                    // should we just delete the file and replace it with a directory?
                    // throw an exception, let the user delete the file manually.
                    throw new ArchiverException( "Expected directory and found file at copy destination of "
                                    + in.getName() + " to " + outFile );
                }
            }
            else if ( !outFile.mkdirs() )
            {
                // Failure, unable to create specified directory for some unknown reason.
                throw new ArchiverException( "Unable to create directory or parent directory of " + outFile );
            }
        }

        outFile.setLastModified( inLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE ? System.currentTimeMillis()
                        : inLastModified );
    }

    protected void cleanUp()
        throws IOException
    {
        super.cleanUp();
        setIncludeEmptyDirs( false );
        setIncludeEmptyDirs( true );
    }

    protected void close()
        throws IOException
    {
    }

    protected String getArchiveType()
    {
        return "directory";
    }
}


package org.codehaus.plexus.archiver.jar;

/**
 *
 * 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.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.console.ConsoleLogger;
import org.codehaus.plexus.util.IOUtil;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;

import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
import static org.codehaus.plexus.archiver.util.Streams.fileOutputStream;

/**
 * Base class for tasks that build archives in JAR file format.
 *
 * @version $Revision$ $Date$
 */
@SuppressWarnings( { "NullableProblems" } )
public class JarArchiver
    extends ZipArchiver
{
    /**
     * the name of the meta-inf dir
     */
    private static final String META_INF_NAME = "META-INF";

    /**
     * The index file name.
     */
    private static final String INDEX_NAME = "META-INF/INDEX.LIST";

    /**
     * The manifest file name.
     */
    private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";

    /**
     * merged manifests added through addConfiguredManifest
     */
    private Manifest configuredManifest;

    /**
     * shadow of the above if upToDate check alters the value
     */
    private Manifest savedConfiguredManifest;

    /**
     * merged manifests added through filesets
     */
    private Manifest filesetManifest;

    /**
     * Manifest of original archive, will be set to null if not in
     * update mode.
     */
    private Manifest originalManifest;

    /**
     * whether to merge fileset manifests;
     * value is true if filesetmanifest is 'merge' or 'mergewithoutmain'
     */
    private FilesetManifestConfig filesetManifestConfig;

    /**
     * whether to merge the main section of fileset manifests;
     * value is true if filesetmanifest is 'merge'
     */
    private boolean mergeManifestsMain = true;

    /**
     * the manifest specified by the 'manifest' attribute *
     */
    private Manifest manifest;

    /**
     * The file found from the 'manifest' attribute.  This can be
     * either the location of a manifest, or the name of a jar added
     * through a fileset.  If its the name of an added jar, the
     * manifest is looked for in META-INF/MANIFEST.MF
     */
    private File manifestFile;

    /**
     * jar index is JDK 1.3+ only
     */
    private boolean index = false;

    /**
     * whether to really create the archive in createEmptyZip, will
     * get set in getResourcesToAdd.
     */
    private boolean createEmpty = false;

    /**
     * Stores all files that are in the root of the archive (i.e. that
     * have a name that doesn't contain a slash) so they can get
     * listed in the index.
     *

     * Will not be filled unless the user has asked for an index.
     */
    private Vector<String> rootEntries;

    /**
     * Path containing jars that shall be indexed in addition to this archive.
     */
    private ArrayList<String> indexJars;

    /**
     * constructor
     */
    public JarArchiver()
    {
        super();
        archiveType = "jar";
        setEncoding( "UTF8" );
        rootEntries = new Vector<String>();
    }

    /**
     * Set whether or not to create an index list for classes.
     * This may speed up classloading in some cases.
     *
     * @param flag true to create an index
     */
    public void setIndex( boolean flag )
    {
        index = flag;
    }

    @SuppressWarnings( { "JavaDoc", "UnusedDeclaration" } )
    @Deprecated // Useless method. Manifests should be UTF-8 by convention. Calling this setter does nothing
    public void setManifestEncoding( String manifestEncoding )
    {
    }

    /**
     * Allows the manifest for the archive file to be provided inline
     * in the build file rather than in an external file.
     *
     * @param newManifest The new manifest
     * @throws ManifestException .
     */
    public void addConfiguredManifest( Manifest newManifest )
        throws ManifestException
    {
        if ( configuredManifest == null )
        {
            configuredManifest = newManifest;
        }
        else
        {
            JdkManifestFactory.merge( configuredManifest, newManifest, false );
        }
        savedConfiguredManifest = configuredManifest;
    }

    /**
     * The manifest file to use. This can be either the location of a manifest, or the name of a jar added through a
     * fileset. If its the name of an added jar, the task expects the manifest to be in the jar at META-INF/MANIFEST.MF.
     *
     * @param manifestFile the manifest file to use.
     * @throws org.codehaus.plexus.archiver.ArchiverException
     *          .
     */
    @SuppressWarnings( { "UnusedDeclaration" } )
    public void setManifest( File manifestFile )
        throws ArchiverException
    {
        if ( !manifestFile.exists() )
        {
            throw new ArchiverException( "Manifest file: " + manifestFile + " does not exist." );
        }

        this.manifestFile = manifestFile;
    }

    private Manifest getManifest( File manifestFile )
        throws ArchiverException
    {
        InputStream in = null;
        try
        {
            in = new FileInputStream( manifestFile );
            return getManifest( in );
        }
        catch ( IOException e )
        {
            throw new ArchiverException( "Unable to read manifest file: " + manifestFile + " (" + e.getMessage() + ")",
                                         e );
        }
        finally
        {
            IOUtil.close( in );
        }
    }

    private Manifest getManifest( InputStream is )
        throws ArchiverException
    {
        try
        {
            return new Manifest( is );
        }
        catch ( IOException e )
        {
            throw new ArchiverException( "Unable to read manifest file" + " (" + e.getMessage() + ")", e );
        }
    }

    /**
     * Behavior when a Manifest is found in a zipfileset or zipgroupfileset file.
     * Valid values are "skip", "merge", and "mergewithoutmain".
     * "merge" will merge all of manifests together, and merge this into any
     * other specified manifests.
     * "mergewithoutmain" merges everything but the Main section of the manifests.
     * Default value is "skip".
     *

     * Note: if this attribute's value is not "skip", the created jar will not
     * be readable by using java.util.jar.JarInputStream
     *
     * @param config setting for found manifest behavior.
     */
    @SuppressWarnings( { "UnusedDeclaration" } )
    public void setFilesetmanifest( FilesetManifestConfig config )
    {
        filesetManifestConfig = config;
        mergeManifestsMain = FilesetManifestConfig.merge == config;

        if ( ( filesetManifestConfig != null ) && filesetManifestConfig != FilesetManifestConfig.skip )
        {

            doubleFilePass = true;
        }
    }

    /**
     * @param indexJar The indexjar
     */
    public void addConfiguredIndexJars( File indexJar )
    {
        if ( indexJars == null )
        {
            indexJars = new ArrayList<String>();
        }
        indexJars.add( indexJar.getAbsolutePath() );
    }

    protected void initZipOutputStream( ZipArchiveOutputStream zOut )
        throws ArchiverException, IOException
    {
        if ( !skipWriting )
        {
            Manifest jarManifest = createManifest();
            writeManifest( zOut, jarManifest );
        }
    }

    protected boolean hasVirtualFiles()
    {
        getLogger().debug( "\n\n\nChecking for jar manifest virtual files...\n\n\n" );
        System.out.flush();

        return ( configuredManifest != null ) || ( manifest != null ) || ( manifestFile != null )
            || super.hasVirtualFiles();
    }

    private Manifest createManifest()
        throws ArchiverException
    {
            Manifest finalManifest = Manifest.getDefaultManifest();

            if ( ( manifest == null ) && ( manifestFile != null ) )
            {
                // if we haven't got the manifest yet, attempt to
                // get it now and have manifest be the final merge
                manifest = getManifest( manifestFile );
            }

        /*
        * Precedence: manifestFile wins over inline manifest,
        * over manifests read from the filesets over the original
        * manifest.
        *
        * merge with null argument is a no-op
        */

        if ( isInUpdateMode() )
        {
            JdkManifestFactory.merge( finalManifest, originalManifest, false );
        }
        JdkManifestFactory.merge( finalManifest, filesetManifest, false );
        JdkManifestFactory.merge( finalManifest, configuredManifest, false );
        JdkManifestFactory.merge( finalManifest, manifest, !mergeManifestsMain );

        return finalManifest;
    }

    private void writeManifest( ZipArchiveOutputStream zOut, Manifest manifest )
        throws IOException, ArchiverException
    {
        for ( Enumeration e = manifest.getWarnings(); e.hasMoreElements(); )
        {
            getLogger().warn( "Manifest warning: " + e.nextElement() );
        }

        zipDir( null, zOut, "META-INF/", DEFAULT_DIR_MODE );
        // time to write the manifest
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        manifest.write( baos );

        ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() );
        super.zipFile( bais, zOut, MANIFEST_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null );
        super.initZipOutputStream( zOut );
    }

    protected void finalizeZipOutputStream( ZipArchiveOutputStream zOut )
        throws IOException, ArchiverException
    {
        if ( index )
        {
            createIndexList( zOut );
        }
    }

    /**
     * Create the index list to speed up classloading.
     * This is a JDK 1.3+ specific feature and is enabled by default. See
     *
     * the JAR index specification for more details.
     *
     * @param zOut the zip stream representing the jar being built.
     * @throws IOException thrown if there is an error while creating the
     *                     index and adding it to the zip stream.
     * @throws org.codehaus.plexus.archiver.ArchiverException
     *                     .
     */
    private void createIndexList( ZipArchiveOutputStream zOut )
        throws IOException, ArchiverException
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // encoding must be UTF8 as specified in the specs.
        PrintWriter writer = new PrintWriter( new OutputStreamWriter( baos, "UTF8" ) );

        // version-info blankline
        writer.println( "JarIndex-Version: 1.0" );
        writer.println();

        // header newline
        writer.println( getDestFile().getName() );

        // filter out META-INF if it doesn't contain anything other than the index and manifest.
        // this is what sun.misc.JarIndex does, guess we ought to be consistent.
        Set<String> filteredDirs = new HashSet<String>( addedDirs.keySet() );
        // our added dirs always have a trailing slash
        if ( filteredDirs.contains( META_INF_NAME + '/' ) )
        {
            boolean add = false;
            for ( String entry : entries.keySet() )
            {
                if ( entry.startsWith( META_INF_NAME + '/' ) && !entry.equals( INDEX_NAME ) && !entry.equals(
                    MANIFEST_NAME ) )
                {
                    add = true;
                    break;
                }
            }
            if ( !add )
            {
                filteredDirs.remove( META_INF_NAME + '/' );
            }
        }
        writeIndexLikeList( new ArrayList<String>( filteredDirs ), rootEntries, writer );
        writer.println();

        if ( indexJars != null )
        {
            java.util.jar.Manifest mf = createManifest();
            String classpath = mf.getMainAttributes().getValue( ManifestConstants.ATTRIBUTE_CLASSPATH );
            String[] cpEntries = null;
            if ( classpath != null )
            {
                StringTokenizer tok = new StringTokenizer( classpath, " " );
                cpEntries = new String[tok.countTokens()];
                int c = 0;
                while ( tok.hasMoreTokens() )
                {
                    cpEntries[c++] = tok.nextToken();
                }
            }

            for ( String indexJar : indexJars )
            {
                String name = findJarName( indexJar, cpEntries );
                if ( name != null )
                {
                    ArrayList<String> dirs = new ArrayList<String>();
                    ArrayList<String> files = new ArrayList<String>();
                    grabFilesAndDirs( indexJar, dirs, files );
                    if ( dirs.size() + files.size() > 0 )
                    {
                        writer.println( name );
                        writeIndexLikeList( dirs, files, writer );
                        writer.println();
                    }
                }
            }
        }

        writer.flush();

        ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() );

        super.zipFile( bais, zOut, INDEX_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null );
    }

    /**
     * Overridden from Zip class to deal with manifests and index lists.
     */
    protected void zipFile( InputStream is, ZipArchiveOutputStream zOut, String vPath, long lastModified, File fromArchive,
                            int mode, String symlinkDestination )
        throws IOException, ArchiverException
    {
        if ( MANIFEST_NAME.equalsIgnoreCase( vPath ) )
        {
            if ( !doubleFilePass || skipWriting )
            {
                filesetManifest( fromArchive, is );
            }
        }
        else if ( INDEX_NAME.equalsIgnoreCase( vPath ) && index )
        {
            getLogger().warn( "Warning: selected " + archiveType + " files include a META-INF/INDEX.LIST which will"
                                  + " be replaced by a newly generated one." );
        }
        else
        {
            if ( index && ( !vPath.contains( "/" ) ) )
            {
                rootEntries.addElement( vPath );
            }
            super.zipFile( is, zOut, vPath, lastModified, fromArchive, mode, symlinkDestination );
        }
    }

    private void filesetManifest( File file, InputStream is )
        throws ArchiverException
    {
        if ( ( manifestFile != null ) && manifestFile.equals( file ) )
        {
            // If this is the same name specified in 'manifest', this
            // is the manifest to use
            getLogger().debug( "Found manifest " + file );
            if ( is != null )
            {
                manifest = getManifest( is );
            }
            else
            {
                manifest = getManifest( file );
            }
        }
        else if ( ( filesetManifestConfig != null ) && filesetManifestConfig != FilesetManifestConfig.skip)
        {
            // we add this to our group of fileset manifests
            getLogger().debug( "Found manifest to merge in file " + file );

            Manifest newManifest;
            if ( is != null )
            {
                newManifest = getManifest( is );
            }
            else
            {
                newManifest = getManifest( file );
            }

            if ( filesetManifest == null )
            {
                filesetManifest = newManifest;
            }
            else
            {
                JdkManifestFactory.merge( filesetManifest, newManifest, false );
            }
        }
    }

    /**
     */
    protected boolean createEmptyZip( File zipFile )
        throws ArchiverException
    {
        if ( !createEmpty )
        {
            return true;
        }

        ZipArchiveOutputStream zOut = null;
        try
        {
            getLogger().debug( "Building MANIFEST-only jar: " + getDestFile().getAbsolutePath() );
            zOut = new ZipArchiveOutputStream( bufferedOutputStream( fileOutputStream( getDestFile(), "jar" ) ));

            zOut.setEncoding( getEncoding() );
            if ( isCompress() )
            {
                zOut.setMethod( ZipArchiveOutputStream.DEFLATED );
            }
            else
            {
                zOut.setMethod( ZipArchiveOutputStream.STORED );
            }
            initZipOutputStream( zOut );
            finalizeZipOutputStream( zOut );
        }
        catch ( IOException ioe )
        {
            throw new ArchiverException( "Could not create almost empty JAR archive (" + ioe.getMessage() + ")", ioe );
        }
        finally
        {
            // Close the output stream.
            IOUtil.close( zOut );
            createEmpty = false;
        }
        return true;
    }

    /**
     * Make sure we don't think we already have a MANIFEST next time this task
     * gets executed.
     *
     * @see ZipArchiver#cleanUp
     */
    protected void cleanUp()
        throws IOException
    {
        super.cleanUp();

        // we want to save this info if we are going to make another pass
        if ( !doubleFilePass || !skipWriting )
        {
            manifest = null;
            configuredManifest = savedConfiguredManifest;
            filesetManifest = null;
            originalManifest = null;
        }
        rootEntries.removeAllElements();
    }

    /**
     * reset to default values.
     *
     * @see ZipArchiver#reset
     */
    public void reset()
    {
        super.reset();
        configuredManifest = null;
        filesetManifestConfig = null;
        mergeManifestsMain = false;
        manifestFile = null;
        index = false;
    }

    public enum FilesetManifestConfig
    {
		skip, merge, mergewithoutmain
    }

    /**
     * Writes the directory entries from the first and the filenames
     * from the second list to the given writer, one entry per line.
     *
     * @param dirs   The directories
     * @param files  The files
     * @param writer The printwriter ;)
     */
    protected final void writeIndexLikeList( List<String> dirs, List<String> files, PrintWriter writer )
    {
        // JarIndex is sorting the directories by ascending order.
        // it has no value but cosmetic since it will be read into a
        // hashtable by the classloader, but we'll do so anyway.
        Collections.sort( dirs );
        Collections.sort( files );
        Iterator iter = dirs.iterator();
        while ( iter.hasNext() )
        {
            String dir = (String) iter.next();

            // try to be smart, not to be fooled by a weird directory name
            dir = dir.replace( '\\', '/' );
            if ( dir.startsWith( "./" ) )
            {
                dir = dir.substring( 2 );
            }
            while ( dir.startsWith( "/" ) )
            {
                dir = dir.substring( 1 );
            }
            int pos = dir.lastIndexOf( '/' );
            if ( pos != -1 )
            {
                dir = dir.substring( 0, pos );
            }

            // name newline
            writer.println( dir );
        }

        iter = files.iterator();
        while ( iter.hasNext() )
        {
            writer.println( iter.next() );
        }
    }

    /**
     * try to guess the name of the given file.
     *

     *
If this jar has a classpath attribute in its manifest, we
     * can assume that it will only require an index of jars listed
     * there.  try to find which classpath entry is most likely the
     * one the given file name points to.

     *

     *
In the absence of a classpath attribute, assume the other
     * files will be placed inside the same directory as this jar and
     * use their basename.

     *

     *
if there is a classpath and the given file doesn't match any
     * of its entries, return null.

     *
     * @param fileName  .
     * @param classpath .
     * @return The guessed name
     */
    protected static String findJarName( String fileName, String[] classpath )
    {
        if ( classpath == null )
        {
            return new File( fileName ).getName();
        }
        fileName = fileName.replace( File.separatorChar, '/' );
        SortedMap<String, String> matches = new TreeMap<String, String>( new Comparator<String>()
        {
            // longest match comes first
            public int compare( String o1, String o2 )
            {
                if ( ( o1 != null ) && ( o2 != null ) )
                {
                    return o2.length() - o1.length();
                }
                return 0;
            }
        } );

        for ( String aClasspath : classpath )
        {
            if ( fileName.endsWith( aClasspath ) )
            {
                matches.put( aClasspath, aClasspath );
            }
            else
            {
                int slash = aClasspath.indexOf( "/" );
                String candidate = aClasspath;
                while ( slash > -1 )
                {
                    candidate = candidate.substring( slash + 1 );
                    if ( fileName.endsWith( candidate ) )
                    {
                        matches.put( candidate, aClasspath );
                        break;
                    }
                    slash = candidate.indexOf( "/" );
                }
            }
        }

        return matches.size() == 0 ? null : matches.get( matches.firstKey() );
    }

    /**
     * Grab lists of all root-level files and all directories
     * contained in the given archive.
     *
     * @param file  .
     * @param files .
     * @param dirs  .
     * @throws java.io.IOException .
     */
    protected static void grabFilesAndDirs( String file, List<String> dirs, List<String> files )
        throws IOException
    {
        File zipFile = new File( file );
        if ( !zipFile.exists() )
        {
            Logger logger = new ConsoleLogger( Logger.LEVEL_INFO, "console" );
            logger.error( "JarArchive skipping non-existing file: " + zipFile.getAbsolutePath() );
        }
        else if ( zipFile.isDirectory() )
        {
            Logger logger = new ConsoleLogger( Logger.LEVEL_INFO, "console" );
            logger.info( "JarArchiver skipping indexJar " + zipFile + " because it is not a jar" );
        }
        else
        {
            org.apache.commons.compress.archivers.zip.ZipFile zf = null;
            try
            {
                zf = new org.apache.commons.compress.archivers.zip.ZipFile( file, "utf-8" );
                Enumeration<ZipArchiveEntry> entries = zf.getEntries();
                HashSet<String> dirSet = new HashSet<String>();
                while ( entries.hasMoreElements() )
                {
                    ZipArchiveEntry ze = entries.nextElement();
                    String name = ze.getName();
                    // avoid index for manifest-only jars.
                    if ( !name.equals( META_INF_NAME ) && !name.equals( META_INF_NAME + '/' ) && !name.equals(
                        INDEX_NAME ) && !name.equals( MANIFEST_NAME ) )
                    {
                        if ( ze.isDirectory() )
                        {
                            dirSet.add( name );
                        }
                        else if ( !name.contains( "/" ) )
                        {
                            files.add( name );
                        }
                        else
                        {
                            // a file, not in the root
                            // since the jar may be one without directory
                            // entries, add the parent dir of this file as
                            // well.
                            dirSet.add( name.substring( 0, name.lastIndexOf( "/" ) + 1 ) );
                        }
                    }
                }
                dirs.addAll( dirSet );
            }
            finally
            {
                if ( zf != null )
                {
                    zf.close();
                }
            }
        }
    }
}


package org.codehaus.plexus.archiver.tar;

/**
 *
 * 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.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.codehaus.plexus.archiver.AbstractUnArchiver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.util.IOUtil;
import org.xerial.snappy.SnappyInputStream;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;

/**
 * @author Emmanuel Venisse
 * @version $Revision$ $Date$
 */
public class TarUnArchiver
    extends AbstractUnArchiver
{
    public TarUnArchiver()
    {
    }

    public TarUnArchiver( File sourceFile )
    {
        super( sourceFile );
    }

    /**
     * compression method
     */
    private UntarCompressionMethod compression = UntarCompressionMethod.NONE;

    /**
     * Set decompression algorithm to use; default=none.
     *

     * Allowable values are
     *

     *
none - no compression

     *
gzip - Gzip compression

     *
bzip2 - Bzip2 compression

     *
snappy - Snappy compression

     *

     *
     * @param method compression method
     */
    public void setCompression( UntarCompressionMethod method )
    {
        compression = method;
    }

    /**
     * No encoding support in Untar.
     */
    public void setEncoding( String encoding )
    {
        getLogger().warn( "The TarUnArchiver doesn't support the encoding attribute" );
    }

    protected void execute()
        throws ArchiverException
    {
        execute( getSourceFile(), getDestDirectory() );
    }

    protected void execute( String path, File outputDirectory )
    {
        execute( new File( path ), getDestDirectory() );
    }

    protected void execute( File sourceFile, File destDirectory )
        throws ArchiverException
    {
        TarArchiveInputStream tis = null;
        try
        {
            getLogger().info( "Expanding: " + sourceFile + " into " + destDirectory );
            TarFile tarFile = new TarFile( sourceFile );
            tis = new TarArchiveInputStream(
                decompress( compression, sourceFile, new BufferedInputStream( new FileInputStream( sourceFile ) ) ) );
            TarArchiveEntry te;
            while ( ( te = tis.getNextTarEntry() ) != null )
            {
                TarResource fileInfo = new TarResource( tarFile, te );
                if ( isSelected( te.getName(), fileInfo ) )
                {
                    final String symlinkDestination = te.isSymbolicLink() ? te.getLinkName() : null;
                    extractFile( sourceFile, destDirectory, tis, te.getName(), te.getModTime(), te.isDirectory(),
                                 te.getMode() != 0 ? te.getMode() : null, symlinkDestination );
                }

            }
            getLogger().debug( "expand complete" );

        }
        catch ( IOException ioe )
        {
            throw new ArchiverException( "Error while expanding " + sourceFile.getAbsolutePath(), ioe );
        }
        finally
        {
            IOUtil.close( tis );
        }
    }

	/**
	 * This method wraps the input stream with the
	 * corresponding decompression method
	 *
	 * @param file    provides location information for BuildException
	 * @param istream input stream
	 * @return input stream with on-the-fly decompression
	 * @throws IOException thrown by GZIPInputStream constructor
	 */
	private InputStream decompress( UntarCompressionMethod compression, final File file, final InputStream istream )
			throws IOException, ArchiverException
	{
		if ( compression == UntarCompressionMethod.GZIP )
		{
			return new GZIPInputStream( istream );
		}
		else if ( compression == UntarCompressionMethod.BZIP2 )
		{
			return new BZip2CompressorInputStream( istream );
		}
        else if ( compression == UntarCompressionMethod.SNAPPY )
        {
            return new SnappyInputStream( istream );
        }
		return istream;
	}

	/**
     * Valid Modes for Compression attribute to Untar Task
     */
    public static enum UntarCompressionMethod
    {
		NONE("none"), GZIP("gzip"), BZIP2("bzip2"), SNAPPY("snappy");

		final String value;

        /**
         * Constructor
         */
        UntarCompressionMethod(String value)
        {
			this.value = value;
        }

    }
}