diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Jar.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Jar.java | 1238 |
1 files changed, 1238 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Jar.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Jar.java new file mode 100644 index 00000000..c2c0f0ed --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Jar.java @@ -0,0 +1,1238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.tools.ant.taskdefs; + +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.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +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.StringTokenizer; +import java.util.TreeMap; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Manifest.Section; +import org.apache.tools.ant.types.ArchiveFileSet; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.ant.types.spi.Service; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.zip.JarMarker; +import org.apache.tools.zip.ZipExtraField; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Creates a JAR archive. + * + * @since Ant 1.1 + * + * @ant.task category="packaging" + */ +public class Jar extends Zip { + /** 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"; + + /** + * List of all known SPI Services + */ + private List<Service> serviceList = new ArrayList<Service>(); + + /** 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 encoding to use when reading in a manifest file */ + private String manifestEncoding; + + /** + * 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 index META-INF/ and its children */ + private boolean indexMetaInf = 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. + * + * @since Ant 1.6 + */ + private Vector<String> rootEntries; + + /** + * Path containing jars that shall be indexed in addition to this archive. + * + * @since Ant 1.6.2 + */ + private Path indexJars; + + // CheckStyle:LineLength OFF - Link is too long. + /** + * Strict mode for checking rules of the JAR-Specification. + * @see http://java.sun.com/j2se/1.3/docs/guide/versioning/spec/VersioningSpecification.html#PackageVersioning + */ + private StrictMode strict = new StrictMode("ignore"); + + // CheckStyle:LineLength ON + + /** + * whether to merge Class-Path attributes. + */ + private boolean mergeClassPaths = false; + + /** + * whether to flatten Class-Path attributes into a single one. + */ + private boolean flattenClassPaths = false; + + /** + * Extra fields needed to make Solaris recognize the archive as a jar file. + * + * @since Ant 1.6.3 + */ + private static final ZipExtraField[] JAR_MARKER = new ZipExtraField[] { + JarMarker.getInstance() + }; + + /** constructor */ + public Jar() { + super(); + archiveType = "jar"; + emptyBehavior = "create"; + setEncoding("UTF8"); + setZip64Mode(Zip64ModeAttribute.NEVER); + rootEntries = new Vector<String>(); + } + + /** + * Not used for jar files. + * @param we not used + * @ant.attribute ignore="true" + */ + public void setWhenempty(WhenEmpty we) { + log("JARs are never empty, they contain at least a manifest file", + Project.MSG_WARN); + } + + /** + * Indicates if a jar file should be created when it would only contain a + * manifest file. + * Possible values are: <code>fail</code> (throw an exception + * and halt the build); <code>skip</code> (do not create + * any archive, but issue a warning); <code>create</code> + * (make an archive with only a manifest file). + * Default is <code>create</code>; + * @param we a <code>WhenEmpty</code> enumerated value + */ + public void setWhenmanifestonly(WhenEmpty we) { + emptyBehavior = we.getValue(); + } + + /** + * Activate the strict mode. When set to <i>true</i> a BuildException + * will be thrown if the Jar-Packaging specification was broken. + * @param strict New value of the strict mode. + * @since Ant 1.7.1 + */ + public void setStrict(StrictMode strict) { + this.strict = strict; + } + + /** + * Set the destination file. + * @param jarFile the destination file + * @deprecated since 1.5.x. + * Use setDestFile(File) instead. + */ + public void setJarfile(File jarFile) { + setDestFile(jarFile); + } + + /** + * Set whether or not to create an index list for classes. + * This may speed up classloading in some cases. + * @param flag a <code>boolean</code> value + */ + public void setIndex(boolean flag) { + index = flag; + } + + /** + * Set whether or not to add META-INF and its children to the index. + * + * <p>Doesn't have any effect if index is false.</p> + * + * <p>Sun's jar implementation used to skip the META-INF directory + * and Ant followed that example. The behavior has been changed + * with Java 5. In order to avoid problems with Ant generated + * jars on Java 1.4 or earlier Ant will not include META-INF + * unless explicitly asked to.</p> + * + * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4408526"> + * jar -i omits service providers in index.list</a> + * @since Ant 1.8.0 + * @param flag a <code>boolean</code> value, defaults to false + */ + public void setIndexMetaInf(boolean flag) { + indexMetaInf = flag; + } + + /** + * The character encoding to use in the manifest file. + * + * @param manifestEncoding the character encoding + */ + public void setManifestEncoding(String manifestEncoding) { + this.manifestEncoding = manifestEncoding; + } + + /** + * Allows the manifest for the archive file to be provided inline + * in the build file rather than in an external file. + * + * @param newManifest an embedded manifest element + * @throws ManifestException on error + */ + public void addConfiguredManifest(Manifest newManifest) + throws ManifestException { + if (configuredManifest == null) { + configuredManifest = newManifest; + } else { + configuredManifest.merge(newManifest, false, mergeClassPaths); + } + 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. + */ + public void setManifest(File manifestFile) { + if (!manifestFile.exists()) { + throw new BuildException("Manifest file: " + manifestFile + + " does not exist.", getLocation()); + } + + this.manifestFile = manifestFile; + } + + private Manifest getManifest(File manifestFile) { + + Manifest newManifest = null; + FileInputStream fis = null; + InputStreamReader isr = null; + try { + fis = new FileInputStream(manifestFile); + if (manifestEncoding == null) { + isr = new InputStreamReader(fis); + } else { + isr = new InputStreamReader(fis, manifestEncoding); + } + newManifest = getManifest(isr); + } catch (UnsupportedEncodingException e) { + throw new BuildException("Unsupported encoding while reading manifest: " + + e.getMessage(), e); + } catch (IOException e) { + throw new BuildException("Unable to read manifest file: " + + manifestFile + + " (" + e.getMessage() + ")", e); + } finally { + FileUtils.close(isr); + } + return newManifest; + } + + /** + * @return null if jarFile doesn't contain a manifest, the + * manifest otherwise. + * @since Ant 1.5.2 + */ + private Manifest getManifestFromJar(File jarFile) throws IOException { + ZipFile zf = null; + try { + zf = new ZipFile(jarFile); + + // must not use getEntry as "well behaving" applications + // must accept the manifest in any capitalization + Enumeration<? extends ZipEntry> e = zf.entries(); + while (e.hasMoreElements()) { + ZipEntry ze = e.nextElement(); + if (ze.getName().equalsIgnoreCase(MANIFEST_NAME)) { + InputStreamReader isr = + new InputStreamReader(zf.getInputStream(ze), "UTF-8"); + return getManifest(isr); + } + } + return null; + } finally { + if (zf != null) { + try { + zf.close(); + } catch (IOException e) { + // TODO - log an error? throw an exception? + } + } + } + } + + private Manifest getManifest(Reader r) { + + Manifest newManifest = null; + try { + newManifest = new Manifest(r); + } catch (ManifestException e) { + log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR); + throw new BuildException("Invalid Manifest: " + manifestFile, + e, getLocation()); + } catch (IOException e) { + throw new BuildException("Unable to read manifest file" + + " (" + e.getMessage() + ")", e); + } + return newManifest; + } + + private boolean jarHasIndex(File jarFile) throws IOException { + ZipFile zf = null; + try { + zf = new ZipFile(jarFile); + Enumeration<? extends ZipEntry> e = zf.entries(); + while (e.hasMoreElements()) { + ZipEntry ze = e.nextElement(); + if (ze.getName().equalsIgnoreCase(INDEX_NAME)) { + return true; + } + } + return false; + } finally { + if (zf != null) { + try { + zf.close(); + } catch (IOException e) { + // TODO - log an error? throw an exception? + } + } + } + } + + /** + * 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. + */ + public void setFilesetmanifest(FilesetManifestConfig config) { + filesetManifestConfig = config; + mergeManifestsMain = "merge".equals(config.getValue()); + + if (filesetManifestConfig != null + && !filesetManifestConfig.getValue().equals("skip")) { + + doubleFilePass = true; + } + } + + /** + * Adds a zipfileset to include in the META-INF directory. + * + * @param fs zipfileset to add + */ + public void addMetainf(ZipFileSet fs) { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix("META-INF/"); + super.addFileset(fs); + } + + /** + * Add a path to index jars. + * @param p a path + * @since Ant 1.6.2 + */ + public void addConfiguredIndexJars(Path p) { + if (indexJars == null) { + indexJars = new Path(getProject()); + } + indexJars.append(p); + } + + /** + * A nested SPI service element. + * @param service the nested element. + * @since Ant 1.7 + */ + public void addConfiguredService(Service service) { + // Check if the service is configured correctly + service.check(); + serviceList.add(service); + } + + /** + * Write SPI Information to JAR + */ + private void writeServices(ZipOutputStream zOut) throws IOException { + for (Service service : serviceList) { + InputStream is = null; + try { + is = service.getAsStream(); + //stolen from writeManifest + super.zipFile(is, zOut, + "META-INF/services/" + service.getType(), + System.currentTimeMillis(), null, + ZipFileSet.DEFAULT_FILE_MODE); + } finally { + // technically this is unnecessary since + // Service.getAsStream returns a ByteArrayInputStream + // and not closing it wouldn't do any harm. + FileUtils.close(is); + } + } + } + + /** + * Whether to merge Class-Path attributes. + * + * @since Ant 1.8.0 + */ + public void setMergeClassPathAttributes(boolean b) { + mergeClassPaths = b; + } + + /** + * Whether to flatten multi-valued attributes (i.e. Class-Path) + * into a single one. + * + * @since Ant 1.8.0 + */ + public void setFlattenAttributes(boolean b) { + flattenClassPaths = b; + } + + /** + * Initialize the zip output stream. + * @param zOut the zip output stream + * @throws IOException on I/O errors + * @throws BuildException on other errors + */ + protected void initZipOutputStream(ZipOutputStream zOut) + throws IOException, BuildException { + + if (!skipWriting) { + Manifest jarManifest = createManifest(); + writeManifest(zOut, jarManifest); + writeServices(zOut); + } + } + + private Manifest createManifest() + throws BuildException { + try { + if (manifest == null) { + if (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); + } + } + + // fileset manifest must come even before the default + // manifest if mergewithoutmain is selected and there is + // no explicit manifest specified - otherwise the Main + // section of the fileset manifest is still merged to the + // final manifest. + boolean mergeFileSetFirst = !mergeManifestsMain + && filesetManifest != null + && configuredManifest == null && manifest == null; + + Manifest finalManifest; + if (mergeFileSetFirst) { + finalManifest = new Manifest(); + finalManifest.merge(filesetManifest, false, mergeClassPaths); + finalManifest.merge(Manifest.getDefaultManifest(), + true, mergeClassPaths); + } else { + finalManifest = Manifest.getDefaultManifest(); + } + + /* + * 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()) { + finalManifest.merge(originalManifest, false, mergeClassPaths); + } + if (!mergeFileSetFirst) { + finalManifest.merge(filesetManifest, false, mergeClassPaths); + } + finalManifest.merge(configuredManifest, !mergeManifestsMain, + mergeClassPaths); + finalManifest.merge(manifest, !mergeManifestsMain, + mergeClassPaths); + + return finalManifest; + + } catch (ManifestException e) { + log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR); + throw new BuildException("Invalid Manifest", e, getLocation()); + } + } + + private void writeManifest(ZipOutputStream zOut, Manifest manifest) + throws IOException { + for (Enumeration<String> e = manifest.getWarnings(); + e.hasMoreElements();) { + log("Manifest warning: " + e.nextElement(), + Project.MSG_WARN); + } + + zipDir((Resource) null, zOut, "META-INF/", ZipFileSet.DEFAULT_DIR_MODE, + JAR_MARKER); + // time to write the manifest + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStreamWriter osw = new OutputStreamWriter(baos, Manifest.JAR_ENCODING); + PrintWriter writer = new PrintWriter(osw); + manifest.write(writer, flattenClassPaths); + if (writer.checkError()) { + throw new IOException("Encountered an error writing the manifest"); + } + writer.close(); + + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + try { + super.zipFile(bais, zOut, MANIFEST_NAME, + System.currentTimeMillis(), null, + ZipFileSet.DEFAULT_FILE_MODE); + } finally { + // not really required + FileUtils.close(bais); + } + super.initZipOutputStream(zOut); + } + + /** + * Finalize the zip output stream. + * This creates an index list if the index attribute is true. + * @param zOut the zip output stream + * @throws IOException on I/O errors + * @throws BuildException on other errors + */ + protected void finalizeZipOutputStream(ZipOutputStream zOut) + throws IOException, BuildException { + + 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 + * <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index"> + * the JAR index specification</a> 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. + */ + private void createIndexList(ZipOutputStream zOut) throws IOException { + 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(zipFile.getName()); + + writeIndexLikeList(new ArrayList<String>(addedDirs.keySet()), + rootEntries, writer); + writer.println(); + + if (indexJars != null) { + Manifest mf = createManifest(); + Manifest.Attribute classpath = + mf.getMainSection().getAttribute(Manifest.ATTRIBUTE_CLASSPATH); + String[] cpEntries = null; + if (classpath != null && classpath.getValue() != null) { + StringTokenizer tok = new StringTokenizer(classpath.getValue(), + " "); + cpEntries = new String[tok.countTokens()]; + int c = 0; + while (tok.hasMoreTokens()) { + cpEntries[c++] = tok.nextToken(); + } + } + String[] indexJarEntries = indexJars.list(); + for (int i = 0; i < indexJarEntries.length; i++) { + String name = findJarName(indexJarEntries[i], cpEntries); + if (name != null) { + ArrayList<String> dirs = new ArrayList<String>(); + ArrayList<String> files = new ArrayList<String>(); + grabFilesAndDirs(indexJarEntries[i], dirs, files); + if (dirs.size() + files.size() > 0) { + writer.println(name); + writeIndexLikeList(dirs, files, writer); + writer.println(); + } + } + } + } + + if (writer.checkError()) { + throw new IOException("Encountered an error writing jar index"); + } + writer.close(); + ByteArrayInputStream bais = + new ByteArrayInputStream(baos.toByteArray()); + try { + super.zipFile(bais, zOut, INDEX_NAME, System.currentTimeMillis(), + null, ZipFileSet.DEFAULT_FILE_MODE); + } finally { + // not really required + FileUtils.close(bais); + } + } + + /** + * Overridden from Zip class to deal with manifests and index lists. + * @param is the stream to read data for the entry from. The + * caller of the method is responsible for closing the stream. + * @param zOut the zip output stream + * @param vPath the name this entry shall have in the archive + * @param lastModified last modification time for the entry. + * @param fromArchive the original archive we are copying this + * entry from, will be null if we are not copying from an archive. + * @param mode the Unix permissions to set. + * @throws IOException on error + */ + protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath, + long lastModified, File fromArchive, int mode) + throws IOException { + if (MANIFEST_NAME.equalsIgnoreCase(vPath)) { + if (isFirstPass()) { + filesetManifest(fromArchive, is); + } + } else if (INDEX_NAME.equalsIgnoreCase(vPath) && index) { + logWhenWriting("Warning: selected " + archiveType + + " files include a " + INDEX_NAME + " which will" + + " be replaced by a newly generated one.", + Project.MSG_WARN); + } else { + if (index && vPath.indexOf("/") == -1) { + rootEntries.addElement(vPath); + } + super.zipFile(is, zOut, vPath, lastModified, fromArchive, mode); + } + } + + private void filesetManifest(File file, InputStream is) throws IOException { + if (manifestFile != null && manifestFile.equals(file)) { + // If this is the same name specified in 'manifest', this + // is the manifest to use + log("Found manifest " + file, Project.MSG_VERBOSE); + try { + if (is != null) { + InputStreamReader isr; + if (manifestEncoding == null) { + isr = new InputStreamReader(is); + } else { + isr = new InputStreamReader(is, manifestEncoding); + } + manifest = getManifest(isr); + } else { + manifest = getManifest(file); + } + } catch (UnsupportedEncodingException e) { + throw new BuildException("Unsupported encoding while reading " + + "manifest: " + e.getMessage(), e); + } + } else if (filesetManifestConfig != null + && !filesetManifestConfig.getValue().equals("skip")) { + // we add this to our group of fileset manifests + logWhenWriting("Found manifest to merge in file " + file, + Project.MSG_VERBOSE); + + try { + Manifest newManifest = null; + if (is != null) { + InputStreamReader isr; + if (manifestEncoding == null) { + isr = new InputStreamReader(is); + } else { + isr = new InputStreamReader(is, manifestEncoding); + } + newManifest = getManifest(isr); + } else { + newManifest = getManifest(file); + } + + if (filesetManifest == null) { + filesetManifest = newManifest; + } else { + filesetManifest.merge(newManifest, false, mergeClassPaths); + } + } catch (UnsupportedEncodingException e) { + throw new BuildException("Unsupported encoding while reading " + + "manifest: " + e.getMessage(), e); + } catch (ManifestException e) { + log("Manifest in file " + file + " is invalid: " + + e.getMessage(), Project.MSG_ERR); + throw new BuildException("Invalid Manifest", e, getLocation()); + } + } else { + // assuming 'skip' otherwise + // don't warn if skip has been requested explicitly, warn if user + // didn't set the attribute + + // Hide warning also as it makes no sense since + // the filesetmanifest attribute itself has been + // hidden + + //int logLevel = filesetManifestConfig == null ? + // Project.MSG_WARN : Project.MSG_VERBOSE; + //log("File " + file + // + " includes a META-INF/MANIFEST.MF which will be ignored. " + // + "To include this file, set filesetManifest to a value other " + // + "than 'skip'.", logLevel); + } + } + + /** + * Collect the resources that are newer than the corresponding + * entries (or missing) in the original archive. + * + * <p>If we are going to recreate the archive instead of updating + * it, all resources should be considered as new, if a single one + * is. Because of this, subclasses overriding this method must + * call <code>super.getResourcesToAdd</code> and indicate with the + * third arg if they already know that the archive is + * out-of-date.</p> + * + * @param rcs The resource collections to grab resources from + * @param zipFile intended archive file (may or may not exist) + * @param needsUpdate whether we already know that the archive is + * out-of-date. Subclasses overriding this method are supposed to + * set this value correctly in their call to + * super.getResourcesToAdd. + * @return an array of resources to add for each fileset passed in as well + * as a flag that indicates whether the archive is uptodate. + * + * @exception BuildException if it likes + */ + protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs, + File zipFile, + boolean needsUpdate) + throws BuildException { + + if (skipWriting) { + // this pass is only there to construct the merged + // manifest this means we claim an update was needed and + // only include the manifests, skipping any uptodate + // checks here deferring them for the second run + Resource[][] manifests = grabManifests(rcs); + int count = 0; + for (int i = 0; i < manifests.length; i++) { + count += manifests[i].length; + } + log("found a total of " + count + " manifests in " + + manifests.length + " resource collections", + Project.MSG_VERBOSE); + return new ArchiveState(true, manifests); + } + + // need to handle manifest as a special check + if (zipFile.exists()) { + // if it doesn't exist, it will get created anyway, don't + // bother with any up-to-date checks. + + try { + originalManifest = getManifestFromJar(zipFile); + if (originalManifest == null) { + log("Updating jar since the current jar has" + + " no manifest", Project.MSG_VERBOSE); + needsUpdate = true; + } else { + Manifest mf = createManifest(); + if (!mf.equals(originalManifest)) { + log("Updating jar since jar manifest has" + + " changed", Project.MSG_VERBOSE); + needsUpdate = true; + } + } + } catch (Throwable t) { + log("error while reading original manifest in file: " + + zipFile.toString() + " due to " + t.getMessage(), + Project.MSG_WARN); + needsUpdate = true; + } + + } else { + // no existing archive + needsUpdate = true; + } + + createEmpty = needsUpdate; + if (!needsUpdate && index) { + try { + needsUpdate = !jarHasIndex(zipFile); + } catch (IOException e) { + //if we couldn't read it, we might as well recreate it? + needsUpdate = true; + } + } + return super.getResourcesToAdd(rcs, zipFile, needsUpdate); + } + + /** + * Create an empty jar file. + * @param zipFile the file to create + * @return true for historic reasons + * @throws BuildException on error + */ + protected boolean createEmptyZip(File zipFile) throws BuildException { + if (!createEmpty) { + return true; + } + + if (emptyBehavior.equals("skip")) { + if (!skipWriting) { + log("Warning: skipping " + archiveType + " archive " + + zipFile + " because no files were included.", + Project.MSG_WARN); + } + return true; + } else if (emptyBehavior.equals("fail")) { + throw new BuildException("Cannot create " + archiveType + + " archive " + zipFile + + ": no files were included.", + getLocation()); + } + + ZipOutputStream zOut = null; + try { + if (!skipWriting) { + log("Building MANIFEST-only jar: " + + getDestFile().getAbsolutePath()); + } + zOut = new ZipOutputStream(getDestFile()); + + zOut.setEncoding(getEncoding()); + if (isCompress()) { + zOut.setMethod(ZipOutputStream.DEFLATED); + } else { + zOut.setMethod(ZipOutputStream.STORED); + } + initZipOutputStream(zOut); + finalizeZipOutputStream(zOut); + } catch (IOException ioe) { + throw new BuildException("Could not create almost empty JAR archive" + + " (" + ioe.getMessage() + ")", ioe, + getLocation()); + } finally { + // Close the output stream. + FileUtils.close(zOut); + createEmpty = false; + } + return true; + } + + /** + * Make sure we don't think we already have a MANIFEST next time this task + * gets executed. + * + * @see Zip#cleanUp + */ + protected void cleanUp() { + super.cleanUp(); + checkJarSpec(); + + // 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(); + } + + // CheckStyle:LineLength OFF - Link is too long. + /** + * Check against packaging spec + * @see "http://java.sun.com/j2se/1.3/docs/guide/versioning/spec/VersioningSpecification.html#PackageVersioning" + */ + // CheckStyle:LineLength ON + private void checkJarSpec() { + String br = System.getProperty("line.separator"); + StringBuffer message = new StringBuffer(); + Section mainSection = (configuredManifest == null) + ? null + : configuredManifest.getMainSection(); + + if (mainSection == null) { + message.append("No Implementation-Title set."); + message.append("No Implementation-Version set."); + message.append("No Implementation-Vendor set."); + } else { + if (mainSection.getAttribute("Implementation-Title") == null) { + message.append("No Implementation-Title set."); + } + if (mainSection.getAttribute("Implementation-Version") == null) { + message.append("No Implementation-Version set."); + } + if (mainSection.getAttribute("Implementation-Vendor") == null) { + message.append("No Implementation-Vendor set."); + } + } + + if (message.length() > 0) { + message.append(br); + message.append("Location: ").append(getLocation()); + message.append(br); + if (strict.getValue().equalsIgnoreCase("fail")) { + throw new BuildException(message.toString(), getLocation()); + } else { + logWhenWriting(message.toString(), strict.getLogLevel()); + } + } + } + + /** + * reset to default values. + * + * @see Zip#reset + * + * @since 1.44, Ant 1.5 + */ + public void reset() { + super.reset(); + emptyBehavior = "create"; + configuredManifest = null; + filesetManifestConfig = null; + mergeManifestsMain = false; + manifestFile = null; + index = false; + } + + /** + * The manifest config enumerated type. + */ + public static class FilesetManifestConfig extends EnumeratedAttribute { + /** + * Get the list of valid strings. + * @return the list of values - "skip", "merge" and "mergewithoutmain" + */ + public String[] getValues() { + return new String[] {"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 a list of directories + * @param files a list of files + * @param writer the writer to write to + * @throws IOException on error + * @since Ant 1.6.2 + */ + protected final void writeIndexLikeList(List<String> dirs, List<String> files, + PrintWriter writer) + throws IOException { + // 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); + } + + // looks like nothing from META-INF should be added + // and the check is not case insensitive. + // see sun.misc.JarIndex + // see also + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4408526 + if (!indexMetaInf && dir.startsWith("META-INF")) { + continue; + } + // name newline + writer.println(dir); + } + + for (String file : files) { + writer.println(file); + } + } + + /** + * try to guess the name of the given file. + * + * <p>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.</p> + * + * <p>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.</p> + * + * <p>if there is a classpath and the given file doesn't match any + * of its entries, return null.</p> + * + * @param fileName the name to look for + * @param classpath the classpath to look in (may be null) + * @return the matching entry, or null if the file is not found + * @since Ant 1.6.2 + */ + protected static String findJarName(String fileName, + String[] classpath) { + if (classpath == null) { + return (new File(fileName)).getName(); + } + fileName = fileName.replace(File.separatorChar, '/'); + TreeMap<String, String> matches = new TreeMap<String, String>(new Comparator<Object>() { + // longest match comes first + public int compare(Object o1, Object o2) { + if (o1 instanceof String && o2 instanceof String) { + return ((String) o2).length() + - ((String) o1).length(); + } + return 0; + } + }); + + for (int i = 0; i < classpath.length; i++) { + if (fileName.endsWith(classpath[i])) { + matches.put(classpath[i], classpath[i]); + } else { + int slash = classpath[i].indexOf("/"); + String candidate = classpath[i]; + while (slash > -1) { + candidate = candidate.substring(slash + 1); + if (fileName.endsWith(candidate)) { + matches.put(candidate, classpath[i]); + break; + } + slash = candidate.indexOf("/"); + } + } + } + + return matches.size() == 0 + ? null : (String) matches.get(matches.firstKey()); + } + + /** + * Grab lists of all root-level files and all directories + * contained in the given archive. + * @param file the zip file to examine + * @param dirs where to place the directories found + * @param files where to place the files found + * @since Ant 1.7 + * @throws IOException on error + */ + protected static void grabFilesAndDirs(String file, List<String> dirs, + List<String> files) + throws IOException { + org.apache.tools.zip.ZipFile zf = null; + try { + zf = new org.apache.tools.zip.ZipFile(file, "utf-8"); + Enumeration<org.apache.tools.zip.ZipEntry> entries = zf.getEntries(); + HashSet<String> dirSet = new HashSet<String>(); + while (entries.hasMoreElements()) { + org.apache.tools.zip.ZipEntry ze = + entries.nextElement(); + String name = ze.getName(); + if (ze.isDirectory()) { + dirSet.add(name); + } else if (name.indexOf("/") == -1) { + 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(); + } + } + } + + private Resource[][] grabManifests(ResourceCollection[] rcs) { + Resource[][] manifests = new Resource[rcs.length][]; + for (int i = 0; i < rcs.length; i++) { + Resource[][] resources = null; + if (rcs[i] instanceof FileSet) { + resources = grabResources(new FileSet[] {(FileSet) rcs[i]}); + } else { + resources = grabNonFileSetResources(new ResourceCollection[] { + rcs[i] + }); + } + for (int j = 0; j < resources[0].length; j++) { + String name = resources[0][j].getName().replace('\\', '/'); + if (rcs[i] instanceof ArchiveFileSet) { + ArchiveFileSet afs = (ArchiveFileSet) rcs[i]; + if (!"".equals(afs.getFullpath(getProject()))) { + name = afs.getFullpath(getProject()); + } else if (!"".equals(afs.getPrefix(getProject()))) { + String prefix = afs.getPrefix(getProject()); + if (!prefix.endsWith("/") && !prefix.endsWith("\\")) { + prefix += "/"; + } + name = prefix + name; + } + } + if (name.equalsIgnoreCase(MANIFEST_NAME)) { + manifests[i] = new Resource[] {resources[0][j]}; + break; + } + } + if (manifests[i] == null) { + manifests[i] = new Resource[0]; + } + } + return manifests; + } + + /** The strict enumerated type. */ + public static class StrictMode extends EnumeratedAttribute { + /** Public no arg constructor. */ + public StrictMode() { + } + /** + * Constructor with an arg. + * @param value the enumerated value as a string. + */ + public StrictMode(String value) { + setValue(value); + } + /** + * Get List of valid strings. + * @return the list of values. + */ + public String[] getValues() { + return new String[]{"fail", "warn", "ignore"}; + } + /** + * @return The log level according to the strict mode. + */ + public int getLogLevel() { + return (getValue().equals("ignore")) ? Project.MSG_VERBOSE : Project.MSG_WARN; + } + } +} |