diff --git a/src/main/org/apache/tools/ant/taskdefs/Jar.java b/src/main/org/apache/tools/ant/taskdefs/Jar.java index 2c95bfae7..f1ad682dc 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Jar.java +++ b/src/main/org/apache/tools/ant/taskdefs/Jar.java @@ -65,11 +65,10 @@ import java.io.*; * * @author James Davidson duncan@x180.com */ - public class Jar extends Zip { - private File manifest; - private boolean manifestAdded; + private Manifest manifest; + private Manifest execManifest; public Jar() { super(); @@ -83,16 +82,33 @@ public class Jar extends Zip { } public void setManifest(File manifestFile) { - manifest = manifestFile; - if (!manifest.exists()) - throw new BuildException("Manifest file: " + manifest + " does not exist."); - - // Create a ZipFileSet for this file, and pass it up. - ZipFileSet fs = new ZipFileSet(); - fs.setDir(new File(manifest.getParent())); - fs.setIncludes(manifest.getName()); - fs.setFullpath("META-INF/MANIFEST.MF"); - super.addFileset(fs); + if (!manifestFile.exists()) { + throw new BuildException("Manifest file: " + manifestFile + " does not exist.", + getLocation()); + } + + InputStream is = null; + try { + is = new FileInputStream(manifestFile); + Manifest newManifest = new Manifest(is); + if (manifest == null) { + manifest = getDefaultManifest(); + } + manifest.merge(newManifest); + } + catch (IOException e) { + throw new BuildException("Unable to read manifest file: " + manifestFile, e); + } + finally { + if (is != null) { + try { + is.close(); + } + catch (IOException e) { + // do nothing + } + } + } } public void addMetainf(ZipFileSet fs) { @@ -106,43 +122,112 @@ public class Jar extends Zip { { // If no manifest is specified, add the default one. if (manifest == null) { - String s = "/org/apache/tools/ant/defaultManifest.mf"; - InputStream in = this.getClass().getResourceAsStream(s); - if ( in == null ) - throw new BuildException ( "Could not find: " + s ); - zipDir(null, zOut, "META-INF/"); - zipFile(in, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis()); + execManifest = null; } - + else { + execManifest = new Manifest(); + execManifest.merge(manifest); + } + zipDir(null, zOut, "META-INF/"); super.initZipOutputStream(zOut); } + private Manifest getDefaultManifest() throws IOException { + String s = "/org/apache/tools/ant/defaultManifest.mf"; + InputStream in = this.getClass().getResourceAsStream(s); + if (in == null) { + throw new BuildException("Could not find: " + s); + } + return new Manifest(in); + } + + protected void finalizeZipOutputStream(ZipOutputStream zOut) + throws IOException, BuildException { + + if (execManifest == null) { + execManifest = getDefaultManifest(); + } + + // time to write the manifest + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(baos); + execManifest.write(writer); + writer.flush(); + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + super.zipFile(bais, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis()); + super.finalizeZipOutputStream(zOut); + + } + + /** + * Handle situation when we encounter a manifest file + * + * If we haven't been given one, we use this one. + * + * If we have, we merge the manifest in, provided it is a new file + * and not the old one from the JAR we are updating + */ + private void zipManifestEntry(InputStream is) throws IOException { + if (execManifest == null) { + execManifest = new Manifest(is); + } + else if (isAddingNewFiles()) { + execManifest.merge(new Manifest(is)); + } + } + protected void zipFile(File file, ZipOutputStream zOut, String vPath) throws IOException { - // If the file being added is META-INF/MANIFEST.MF, we warn if it's not the - // one specified in the "manifest" attribute - or if it's being added twice, - // meaning the same file is specified by the "manifeset" attribute and in - // a element. + // If the file being added is META-INF/MANIFEST.MF, we merge it with the + // current manifest if (vPath.equalsIgnoreCase("META-INF/MANIFEST.MF")) { - if (manifest == null || !manifest.equals(file) || manifestAdded) { - log("Warning: selected "+archiveType+" files include a META-INF/MANIFEST.MF which will be ignored " + - "(please use manifest attribute to "+archiveType+" task)", Project.MSG_WARN); - } else { - super.zipFile(file, zOut, vPath); - manifestAdded = true; + InputStream is = null; + try { + is = new FileInputStream(file); + zipManifestEntry(is); + } + catch (IOException e) { + throw new BuildException("Unable to read manifest file: " + file, e); + } + finally { + if (is != null) { + try { + is.close(); + } + catch (IOException e) { + // do nothing + } + } } } else { super.zipFile(file, zOut, vPath); } } + protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath, long lastModified) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we merge it with the + // current manifest + if (vPath.equalsIgnoreCase("META-INF/MANIFEST.MF")) { + try { + zipManifestEntry(is); + } + catch (IOException e) { + throw new BuildException("Unable to read manifest file: ", e); + } + } else { + super.zipFile(is, zOut, vPath, lastModified); + } + } + /** * Make sure we don't think we already have a MANIFEST next time this task * gets executed. */ protected void cleanUp() { - manifestAdded = false; super.cleanUp(); } } diff --git a/src/main/org/apache/tools/ant/taskdefs/Manifest.java b/src/main/org/apache/tools/ant/taskdefs/Manifest.java new file mode 100644 index 000000000..2d55f95d3 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/Manifest.java @@ -0,0 +1,329 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs; + +import java.util.*; +import java.io.*; + +/** + * Class to manage Manifest information + * + * @author Conor MacNeill + */ +public class Manifest { + static public final String ATTR_MANIFEST_VERSION = "Manifest-Version"; + static public final String ATTR_SIGNATURE_VERSION = "Signature-Version"; + static public final String ATTR_NAME = "Name"; + static public final String ATTR_FROM = "From"; + static public final String DEFAULT_MANIFEST_VERSION = "1.0"; + static public final int MAX_LINE_LENGTH = 70; + + /** + * Class to hold manifest attributes + */ + static private class Attribute { + /** The attribute's name */ + private String name = null; + + /** The attribute's value */ + private String value = null; + + public Attribute() { + } + + public Attribute(String line) throws IOException { + parse(line); + } + + public Attribute(String name, String value) { + this.name = name; + this.value = value; + } + + public void parse(String line) throws IOException { + int index = line.indexOf(": "); + if (index == -1) { + throw new IOException("Manifest line \"" + line + "\" is not valid"); + } + name = line.substring(0, index); + value = line.substring(index + 2); + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setValue(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void addContinuation(String line) { + value += line.substring(1); + } + + public void write(PrintWriter writer) throws IOException { + String line = name + ": " + value; + while (line.getBytes().length > MAX_LINE_LENGTH) { + // try to find a MAX_LINE_LENGTH byte section + int breakIndex = MAX_LINE_LENGTH; + String section = line.substring(0, breakIndex); + while (section.getBytes().length > MAX_LINE_LENGTH && breakIndex > 0) { + breakIndex--; + section = line.substring(0, breakIndex); + } + if (breakIndex == 0) { + throw new IOException("Unable to write manifest line " + name + ": " + value); + } + writer.println(section); + line = " " + line.substring(breakIndex); + } + writer.println(line); + } + } + + /** + * Class to represent an individual section in the + * Manifest + */ + static private class Section { + private String name = null; + + private Hashtable attributes = new Hashtable(); + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void read(BufferedReader reader) throws IOException { + Attribute attribute = null; + while (true) { + String line = reader.readLine(); + if (line == null || line.length() == 0) { + return; + } + if (line.charAt(0) == ' ') { + // continuation line + if (attribute == null) { + throw new IOException("Can't start an attribute with a continuation line " + line); + } + attribute.addContinuation(line); + } + else { + attribute = new Attribute(line); + if (name == null && attribute.getName().equalsIgnoreCase(ATTR_NAME)) { + throw new IOException("The " + ATTR_NAME + " header may not occur in the main section "); + } + + if (attribute.getName().toLowerCase().startsWith(ATTR_FROM.toLowerCase())) { + throw new IOException("Attribute names may not start with " + ATTR_FROM); + } + + addAttribute(attribute); + } + } + } + + public void merge(Section section) throws IOException { + if (name == null && section.getName() != null || + name != null && !(name.equalsIgnoreCase(section.getName()))) { + throw new IOException("Unable to merge sections with different names"); + } + + for (Enumeration e = section.attributes.keys(); e.hasMoreElements();) { + String attributeName = (String)e.nextElement(); + // the merge file always wins + attributes.put(attributeName, section.attributes.get(attributeName)); + } + } + + public void write(PrintWriter writer) throws IOException { + if (name != null) { + Attribute nameAttr = new Attribute(ATTR_NAME, name); + nameAttr.write(writer); + } + for (Enumeration e = attributes.elements(); e.hasMoreElements();) { + Attribute attribute = (Attribute)e.nextElement(); + attribute.write(writer); + } + writer.println(); + } + + public String getAttributeValue(String attributeName) { + Attribute attribute = (Attribute)attributes.get(attributeName.toLowerCase()); + if (attribute == null) { + return null; + } + return attribute.getValue(); + } + + public void removeAttribute(String attributeName) { + attributes.remove(attributeName.toLowerCase()); + } + + public void addAttribute(Attribute attribute) throws IOException { + if (attributes.containsKey(attribute.getName().toLowerCase())) { + throw new IOException("The attribute \"" + attribute.getName() + "\" may not occur more than" + + " once in the same section"); + } + attributes.put(attribute.getName().toLowerCase(), attribute); + } + } + + + private String manifestVersion = DEFAULT_MANIFEST_VERSION; + private Section mainSection = new Section(); + private Hashtable sections = new Hashtable(); + + public Manifest() { + } + + /** + * Read a manifest file from the given input stream + * + * @param is the input stream from which the Manifest is read + */ + public Manifest(InputStream is) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line = reader.readLine(); + if (line == null) { + return; + } + + // This should be the manifest version + Attribute version = new Attribute(line); + if (!version.getName().equalsIgnoreCase(ATTR_MANIFEST_VERSION)) { + throw new IOException("Manifest must start with \"" + ATTR_MANIFEST_VERSION + + "\" and not \"" + line + "\""); + } + manifestVersion = version.getValue(); + mainSection.read(reader); + + while ((line = reader.readLine()) != null) { + if (line.length() == 0) { + continue; + } + Attribute sectionName = new Attribute(line); + if (!sectionName.getName().equalsIgnoreCase(ATTR_NAME)) { + throw new IOException("Manifest sections should start with a \"" + ATTR_NAME + + "\" attribute and not \"" + sectionName.getName() + "\""); + } + + Section section = new Section(); + section.setName(sectionName.getValue()); + section.read(reader); + sections.put(section.getName().toLowerCase(), section); + } + } + + /** + * Merge the contents of the given manifest into this manifest + */ + public void merge(Manifest other) throws IOException { + manifestVersion = other.manifestVersion; + mainSection.merge(other.mainSection); + for (Enumeration e = other.sections.keys(); e.hasMoreElements();) { + String sectionName = (String)e.nextElement(); + Section ourSection = (Section)sections.get(sectionName); + Section otherSection = (Section)other.sections.get(sectionName); + if (ourSection == null) { + sections.put(sectionName.toLowerCase(), otherSection); + } + else { + ourSection.merge(otherSection); + } + } + } + + public void write(PrintWriter writer) throws IOException { + writer.println(ATTR_MANIFEST_VERSION + ": " + manifestVersion); + String signatureVersion = mainSection.getAttributeValue(ATTR_SIGNATURE_VERSION); + if (signatureVersion != null) { + writer.println(ATTR_SIGNATURE_VERSION + ": " + signatureVersion); + mainSection.removeAttribute(ATTR_SIGNATURE_VERSION); + } + mainSection.write(writer); + if (signatureVersion != null) { + mainSection.addAttribute(new Attribute(ATTR_SIGNATURE_VERSION, signatureVersion)); + } + + for (Enumeration e = sections.elements(); e.hasMoreElements();) { + Section section = (Section)e.nextElement(); + section.write(writer); + } + } + + public String toString() { + StringWriter sw = new StringWriter(); + try { + write(new PrintWriter(sw)); + } + catch (IOException e) { + return null; + } + return sw.toString(); + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/Zip.java b/src/main/org/apache/tools/ant/taskdefs/Zip.java index b9a6540bb..33ca4d394 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Zip.java +++ b/src/main/org/apache/tools/ant/taskdefs/Zip.java @@ -87,7 +87,12 @@ public class Zip extends MatchingTask { private Vector filesets = new Vector (); private Hashtable addedDirs = new Hashtable(); private Vector addedFiles = new Vector(); - + + /** true when we are adding new files into the Zip file, as opposed to + adding back the unchanged files */ + private boolean addingNewFiles; + + /** * Encoding to use for filenames, defaults to the platform's * default encoding. @@ -190,42 +195,46 @@ public class Zip extends MatchingTask { } // Renamed version of original file, if it exists - File renamedFile=null; + File renamedFile = null; // Whether or not an actual update is required - // we don't need to update if the original file doesn't exist - boolean reallyDoUpdate=false; + + addingNewFiles = true; + boolean reallyDoUpdate = false; if (doUpdate && zipFile.exists()) { - reallyDoUpdate=true; + reallyDoUpdate = true; int i; for (i=0; i < 1000; i++) { - renamedFile = new File (zipFile.getParent(), "tmp."+i); + renamedFile = new File(zipFile.getParent(), "tmp."+i); - if (!renamedFile.exists()) + if (!renamedFile.exists()) { break; + } } - if (i==1000) - throw new BuildException - ("Can't find temporary filename to rename old file to."); + if (i == 1000) { + throw new BuildException("Can't find available temporary filename to which to rename old file."); + } + try { - if (!zipFile.renameTo (renamedFile)) - throw new BuildException - ("Unable to rename old file to temporary file"); + if (!zipFile.renameTo(renamedFile)) { + throw new BuildException("Unable to rename old file to temporary file"); + } } catch (SecurityException e) { - throw new BuildException - ("Not allowed to rename old file to temporary file"); + throw new BuildException("Not allowed to rename old file to temporary file"); } } // Create the scanners to pass to isUpToDate(). Vector dss = new Vector (); - if (baseDir != null) + if (baseDir != null) { dss.addElement(getDirectoryScanner(baseDir)); + } for (int i=0; i