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

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

/**
 *
 * 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 javax.inject.Named;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
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 org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
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 static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
import static org.codehaus.plexus.archiver.util.Streams.fileInputStream;
import static org.codehaus.plexus.archiver.util.Streams.fileOutputStream;

/**
 * Base class for tasks that build archives in JAR file format.
 */
@Named("jar")
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.
     *
     * @deprecated See JDK-8302819
     */
    @Deprecated
    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
     *
     * @deprecated See JDK-8302819
     */
    @Deprecated
    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 final List rootEntries; /** * Path containing jars that shall be indexed in addition to this archive. * * @deprecated See JDK-8302819 */ @Deprecated private List indexJars; /** * Creates a minimal default manifest with {@code Manifest-Version: 1.0} only. */ private boolean minimalDefaultManifest = false; /** * constructor */ public JarArchiver() { super(); archiveType = "jar"; setEncoding("UTF8"); rootEntries = new ArrayList<>(); } /** * 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 * @deprecated See JDK-8302819 */ @Deprecated public void setIndex(boolean flag) { index = flag; } /** * Set whether the default manifest is minimal, thus having only {@code Manifest-Version: 1.0} in it. * * @param minimalDefaultManifest true to create minimal default manifest */ public void setMinimalDefaultManifest(boolean minimalDefaultManifest) { this.minimalDefaultManifest = minimalDefaultManifest; } @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 { try (InputStream in = fileInputStream(manifestFile)) { return getManifest(in); } catch (IOException e) { throw new ArchiverException( "Unable to read manifest file: " + manifestFile + " (" + e.getMessage() + ")", e); } } 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 * @deprecated See JDK-8302819 */ @Deprecated 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(); } /** * Creates the manifest to be added to the JAR archive. * Sub-classes may choose to override this method * in order to inspect or modify the JAR manifest file. * * @return the manifest for the JAR archive. * * @throws ArchiverException */ protected Manifest createManifest() throws ArchiverException { Manifest finalManifest = Manifest.getDefaultManifest(minimalDefaultManifest); 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(128); manifest.write(baos); InputStreamSupplier in = () -> new ByteArrayInputStream(baos.toByteArray()); super.zipFile(in, 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 * @deprecated See JDK-8302819 */ @Deprecated private void createIndexList(ConcurrentJarCreator zOut) throws IOException, ArchiverException { ByteArrayOutputStream baos = new ByteArrayOutputStream(128); // encoding must be UTF8 as specified in the specs. PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8)); // 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) { List dirs = new ArrayList<>(); List files = new ArrayList<>(); grabFilesAndDirs(indexJar, dirs, files); if (dirs.size() + files.size() > 0) { writer.println(name); writeIndexLikeList(dirs, files, writer); writer.println(); } } } } writer.flush(); InputStreamSupplier in = () -> new ByteArrayInputStream(baos.toByteArray()); super.zipFile(in, 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) { try (InputStream manifestInputStream = is.get()) { filesetManifest(fromArchive, manifestInputStream); } } } 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.add(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( isRecompressAddedZips(), 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.clear(); } /** * 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 ;) * @deprecated See JDK-8302819 */ @Deprecated 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, '/'); // longest match comes first SortedMap matches = new TreeMap<>(Comparator.comparingInt(String::length).reversed()); 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 */ private void grabFilesAndDirs(String file, List dirs, List files) throws IOException { File zipFile = new File(file); if (!zipFile.exists()) { getLogger().error("JarArchive skipping non-existing file: " + zipFile.getAbsolutePath()); } else if (zipFile.isDirectory()) { getLogger().info("JarArchiver skipping indexJar " + zipFile + " because it is not a jar"); } else { try (ZipFile zf = new 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); } } } /** * Override the behavior of the Zip Archiver to match the output of the JAR tool. * * @param zipEntry to set the last modified time * @param lastModifiedTime to set in the zip entry only if {@link #getLastModifiedTime()} returns null */ @Override protected void setZipEntryTime(ZipArchiveEntry zipEntry, long lastModifiedTime) { if (getLastModifiedTime() != null) { lastModifiedTime = getLastModifiedTime().toMillis(); } // The JAR tool does not round up, so we keep that behavior here (JDK-8277755). zipEntry.setTime(lastModifiedTime); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy