All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.archiver.jar.JarArchiver Maven / Gradle / Ivy

There is a newer version: 4.10.0
Show newest version
/**
 *
 * 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.
 */
package org.codehaus.plexus.archiver.jar;

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.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
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 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.
 */
@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 rootEntries; /** * Path containing jars that shall be indexed in addition to this archive. */ private ArrayList indexJars; /** * constructor */ public JarArchiver() { super(); archiveType = "jar"; setEncoding( "UTF8" ); rootEntries = new Vector(); } /** * 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 ); final Manifest mf = getManifest( in ); in.close(); in = null; return mf; } 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(); } indexJars.add( indexJar.getAbsolutePath() ); } @Override protected void initZipOutputStream( ConcurrentJarCreator zOut ) throws ArchiverException, IOException { if ( !skipWriting ) { Manifest jarManifest = createManifest(); writeManifest( zOut, jarManifest ); } } @Override 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( ConcurrentJarCreator 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, getEncoding() ); // time to write the manifest ByteArrayOutputStream baos = new ByteArrayOutputStream(); manifest.write( baos ); ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); super.zipFile( createInputStreamSupplier( bais ), zOut, MANIFEST_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null, false ); super.initZipOutputStream( zOut ); } @Override protected void finalizeZipOutputStream( ConcurrentJarCreator 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( ConcurrentJarCreator 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 filteredDirs = addedDirs.allAddedDirs(); // 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( 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 dirs = new ArrayList(); ArrayList files = new ArrayList(); 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( createInputStreamSupplier( bais ), zOut, INDEX_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null, true ); } /** * Overridden from Zip class to deal with manifests and index lists. */ @Override protected void zipFile( InputStreamSupplier is, ConcurrentJarCreator zOut, String vPath, long lastModified, File fromArchive, int mode, String symlinkDestination, boolean addInParallel ) throws IOException, ArchiverException { if ( MANIFEST_NAME.equalsIgnoreCase( vPath ) ) { if ( !doubleFilePass || skipWriting ) { filesetManifest( fromArchive, is.get() ); } } 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, addInParallel ); } } 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 ); } } } @Override protected boolean createEmptyZip( File zipFile ) throws ArchiverException { if ( !createEmpty ) { return true; } try { getLogger().debug( "Building MANIFEST-only jar: " + getDestFile().getAbsolutePath() ); zipArchiveOutputStream = new ZipArchiveOutputStream( bufferedOutputStream( fileOutputStream( getDestFile(), "jar" ) ) ); zipArchiveOutputStream.setEncoding( getEncoding() ); if ( isCompress() ) { zipArchiveOutputStream.setMethod( ZipArchiveOutputStream.DEFLATED ); } else { zipArchiveOutputStream.setMethod( ZipArchiveOutputStream.STORED ); } ConcurrentJarCreator ps = new ConcurrentJarCreator( Runtime.getRuntime().availableProcessors() ); initZipOutputStream( ps ); finalizeZipOutputStream( ps ); } 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 */ @Override 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 */ @Override 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 dirs, List 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 ); for ( String dir : dirs ) { // 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 ); } for ( String file : files ) { writer.println( file ); } } /** * 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 matches = new TreeMap( new Comparator() { // longest match comes first @Override 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 dirs, List 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 entries = zf.getEntries(); HashSet dirSet = new HashSet(); 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(); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy