aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Jar.java
diff options
context:
space:
mode:
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.java1238
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;
+ }
+ }
+}