diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Manifest.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Manifest.java | 1183 |
1 files changed, 1183 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Manifest.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Manifest.java new file mode 100644 index 00000000..06c74ddc --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Manifest.java @@ -0,0 +1,1183 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Vector; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.CollectionUtils; +import org.apache.tools.ant.util.FileUtils; + +/** + * Holds the data of a jar manifest. + * + * Manifests are processed according to the + * {@link <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html">Jar + * file specification.</a>}. + * Specifically, a manifest element consists of + * a set of attributes and sections. These sections in turn may contain + * attributes. Note in particular that this may result in manifest lines + * greater than 72 bytes being wrapped and continued on the next + * line. If an application can not handle the continuation mechanism, it + * is a defect in the application, not this task. + * + * + * @since Ant 1.4 + */ +public class Manifest { + /** The standard manifest version header */ + public static final String ATTRIBUTE_MANIFEST_VERSION + = "Manifest-Version"; + + /** The standard Signature Version header */ + public static final String ATTRIBUTE_SIGNATURE_VERSION + = "Signature-Version"; + + /** The Name Attribute is the first in a named section */ + public static final String ATTRIBUTE_NAME = "Name"; + + /** The From Header is disallowed in a Manifest */ + public static final String ATTRIBUTE_FROM = "From"; + + /** The Class-Path Header is special - it can be duplicated */ + public static final String ATTRIBUTE_CLASSPATH = "Class-Path"; + + /** Default Manifest version if one is not specified */ + public static final String DEFAULT_MANIFEST_VERSION = "1.0"; + + /** The max length of a line in a Manifest */ + public static final int MAX_LINE_LENGTH = 72; + + /** + * Max length of a line section which is continued. Need to allow + * for the CRLF. + */ + public static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2; + + /** The End-Of-Line marker in manifests */ + public static final String EOL = "\r\n"; + /** Error for attributes */ + public static final String ERROR_FROM_FORBIDDEN = "Manifest attributes should not start " + + "with \"" + ATTRIBUTE_FROM + "\" in \""; + + /** Encoding to be used for JAR files. */ + public static final String JAR_ENCODING = "UTF-8"; + + private static final String ATTRIBUTE_MANIFEST_VERSION_LC = + ATTRIBUTE_MANIFEST_VERSION.toLowerCase(Locale.ENGLISH); + private static final String ATTRIBUTE_NAME_LC = + ATTRIBUTE_NAME.toLowerCase(Locale.ENGLISH); + private static final String ATTRIBUTE_FROM_LC = + ATTRIBUTE_FROM.toLowerCase(Locale.ENGLISH); + private static final String ATTRIBUTE_CLASSPATH_LC = + ATTRIBUTE_CLASSPATH.toLowerCase(Locale.ENGLISH); + + /** + * An attribute for the manifest. + * Those attributes that are not nested into a section will be added to the "Main" section. + */ + public static class Attribute { + + /** + * Maximum length of the name to have the value starting on the same + * line as the name. This to stay under 72 bytes total line length + * (including CRLF). + */ + private static final int MAX_NAME_VALUE_LENGTH = 68; + + /** + * Maximum length of the name according to the jar specification. + * In this case the first line will have 74 bytes total line length + * (including CRLF). This conflicts with the 72 bytes total line length + * max, but is the only possible conclusion from the manifest specification, if + * names with 70 bytes length are allowed, have to be on the first line, and + * have to be followed by ": ". + */ + private static final int MAX_NAME_LENGTH = 70; + + /** The attribute's name */ + private String name = null; + + /** The attribute's value */ + private Vector<String> values = new Vector<String>(); + + /** + * For multivalued attributes, this is the index of the attribute + * currently being defined. + */ + private int currentIndex = 0; + + /** + * Construct an empty attribute */ + public Attribute() { + } + + /** + * Construct an attribute by parsing a line from the Manifest + * + * @param line the line containing the attribute name and value + * + * @throws ManifestException if the line is not valid + */ + public Attribute(String line) throws ManifestException { + parse(line); + } + + /** + * Construct a manifest by specifying its name and value + * + * @param name the attribute's name + * @param value the Attribute's value + */ + public Attribute(String name, String value) { + this.name = name; + setValue(value); + } + + /** + * @see java.lang.Object#hashCode + * @return a hashcode based on the key and values. + */ + @Override + public int hashCode() { + int hashCode = 0; + + if (name != null) { + hashCode += getKey().hashCode(); + } + + hashCode += values.hashCode(); + return hashCode; + } + + /** + * @param rhs the object to check for equality. + * @see java.lang.Object#equals + * @return true if the key and values are the same. + */ + @Override + public boolean equals(Object rhs) { + if (rhs == null || rhs.getClass() != getClass()) { + return false; + } + + if (rhs == this) { + return true; + } + + Attribute rhsAttribute = (Attribute) rhs; + String lhsKey = getKey(); + String rhsKey = rhsAttribute.getKey(); + if ((lhsKey == null && rhsKey != null) + || (lhsKey != null && !lhsKey.equals(rhsKey))) { + return false; + } + + return values.equals(rhsAttribute.values); + } + + /** + * Parse a line into name and value pairs + * + * @param line the line to be parsed + * + * @throws ManifestException if the line does not contain a colon + * separating the name and value + */ + public void parse(String line) throws ManifestException { + int index = line.indexOf(": "); + if (index == -1) { + throw new ManifestException("Manifest line \"" + line + + "\" is not valid as it does not " + + "contain a name and a value separated by ': ' "); + } + name = line.substring(0, index); + setValue(line.substring(index + 2)); + } + + /** + * Set the Attribute's name; required + * + * @param name the attribute's name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get the Attribute's name + * + * @return the attribute's name. + */ + public String getName() { + return name; + } + + /** + * Get the attribute's Key - its name in lower case. + * + * @return the attribute's key. + */ + public String getKey() { + if (name == null) { + return null; + } + return name.toLowerCase(Locale.ENGLISH); + } + + /** + * Set the Attribute's value; required + * + * @param value the attribute's value + */ + public void setValue(String value) { + if (currentIndex >= values.size()) { + values.addElement(value); + currentIndex = values.size() - 1; + } else { + values.setElementAt(value, currentIndex); + } + } + + /** + * Get the Attribute's value. + * + * @return the attribute's value. + */ + public String getValue() { + if (values.size() == 0) { + return null; + } + + String fullValue = ""; + for (Enumeration<String> e = getValues(); e.hasMoreElements();) { + String value = e.nextElement(); + fullValue += value + " "; + } + return fullValue.trim(); + } + + /** + * Add a new value to this attribute - making it multivalued. + * + * @param value the attribute's additional value + */ + public void addValue(String value) { + currentIndex++; + setValue(value); + } + + /** + * Get all the attribute's values. + * + * @return an enumeration of the attributes values + */ + public Enumeration<String> getValues() { + return values.elements(); + } + + /** + * Add a continuation line from the Manifest file. + * + * When lines are too long in a manifest, they are continued on the + * next line by starting with a space. This method adds the continuation + * data to the attribute value by skipping the first character. + * + * @param line the continuation line. + */ + public void addContinuation(String line) { + String currentValue = values.elementAt(currentIndex); + setValue(currentValue + line.substring(1)); + } + + /** + * Write the attribute out to a print writer without + * flattening multi-values attributes (i.e. Class-Path). + * + * @param writer the Writer to which the attribute is written + * + * @throws IOException if the attribute value cannot be written + */ + public void write(PrintWriter writer) throws IOException { + write(writer, false); + } + + /** + * Write the attribute out to a print writer. + * + * @param writer the Writer to which the attribute is written + * @param flatten whether to collapse multi-valued attributes + * (i.e. potentially Class-Path) Class-Path into a + * single attribute. + * + * @throws IOException if the attribute value cannot be written + * @since Ant 1.8.0 + */ + public void write(PrintWriter writer, boolean flatten) + throws IOException { + if (!flatten) { + for (Enumeration<String> e = getValues(); e.hasMoreElements();) { + writeValue(writer, e.nextElement()); + } + } else { + writeValue(writer, getValue()); + } + } + + /** + * Write a single attribute value out + * + * @param writer the Writer to which the attribute is written + * @param value the attribute value + * + * @throws IOException if the attribute value cannot be written + */ + private void writeValue(PrintWriter writer, String value) + throws IOException { + String line = null; + int nameLength = name.getBytes(JAR_ENCODING).length; + if (nameLength > MAX_NAME_VALUE_LENGTH) { + if (nameLength > MAX_NAME_LENGTH) { + throw new IOException("Unable to write manifest line " + + name + ": " + value); + } + writer.print(name + ": " + EOL); + line = " " + value; + } else { + line = name + ": " + value; + } + while (line.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH) { + // try to find a MAX_LINE_LENGTH byte section + int breakIndex = MAX_SECTION_LENGTH; + if (breakIndex >= line.length()) { + breakIndex = line.length() - 1; + } + String section = line.substring(0, breakIndex); + while (section.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH + && breakIndex > 0) { + breakIndex--; + section = line.substring(0, breakIndex); + } + if (breakIndex == 0) { + throw new IOException("Unable to write manifest line " + + name + ": " + value); + } + writer.print(section + EOL); + line = " " + line.substring(breakIndex); + } + writer.print(line + EOL); + } + } + + /** + * A manifest section - you can nest attribute elements into sections. + * A section consists of a set of attribute values, + * separated from other sections by a blank line. + */ + public static class Section { + /** Warnings for this section */ + private Vector<String> warnings = new Vector<String>(); + + /** + * The section's name if any. The main section in a + * manifest is unnamed. + */ + private String name = null; + + /** The section's attributes.*/ + private Map<String, Attribute> attributes = new LinkedHashMap<String, Attribute>(); + + /** + * The name of the section; optional -default is the main section. + * @param name the section's name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get the Section's name. + * + * @return the section's name. + */ + public String getName() { + return name; + } + + /** + * Read a section through a reader. + * + * @param reader the reader from which the section is read + * + * @return the name of the next section if it has been read as + * part of this section - This only happens if the + * Manifest is malformed. + * + * @throws ManifestException if the section is not valid according + * to the JAR spec + * @throws IOException if the section cannot be read from the reader. + */ + public String read(BufferedReader reader) + throws ManifestException, IOException { + Attribute attribute = null; + while (true) { + String line = reader.readLine(); + if (line == null || line.length() == 0) { + return null; + } + if (line.charAt(0) == ' ') { + // continuation line + if (attribute == null) { + if (name != null) { + // a continuation on the first line is a + // continuation of the name - concatenate this + // line and the name + name += line.substring(1); + } else { + throw new ManifestException("Can't start an " + + "attribute with a continuation line " + line); + } + } else { + attribute.addContinuation(line); + } + } else { + attribute = new Attribute(line); + String nameReadAhead = addAttributeAndCheck(attribute); + // refresh attribute in case of multivalued attributes. + attribute = getAttribute(attribute.getKey()); + if (nameReadAhead != null) { + return nameReadAhead; + } + } + } + } + + /** + * Merge in another section without merging Class-Path attributes. + * + * @param section the section to be merged with this one. + * + * @throws ManifestException if the sections cannot be merged. + */ + public void merge(Section section) throws ManifestException { + merge(section, false); + } + + /** + * Merge in another section + * + * @param section the section to be merged with this one. + * @param mergeClassPaths whether Class-Path attributes should + * be merged. + * + * @throws ManifestException if the sections cannot be merged. + */ + public void merge(Section section, boolean mergeClassPaths) + throws ManifestException { + if (name == null && section.getName() != null + || (name != null && section.getName() != null + && !(name.toLowerCase(Locale.ENGLISH) + .equals(section.getName().toLowerCase(Locale.ENGLISH)))) + ) { + throw new ManifestException("Unable to merge sections " + + "with different names"); + } + + Enumeration<String> e = section.getAttributeKeys(); + Attribute classpathAttribute = null; + while (e.hasMoreElements()) { + String attributeName = e.nextElement(); + Attribute attribute = section.getAttribute(attributeName); + if (attributeName.equalsIgnoreCase(ATTRIBUTE_CLASSPATH)) { + if (classpathAttribute == null) { + classpathAttribute = new Attribute(); + classpathAttribute.setName(ATTRIBUTE_CLASSPATH); + } + Enumeration<String> cpe = attribute.getValues(); + while (cpe.hasMoreElements()) { + String value = cpe.nextElement(); + classpathAttribute.addValue(value); + } + } else { + // the merge file always wins + storeAttribute(attribute); + } + } + + if (classpathAttribute != null) { + if (mergeClassPaths) { + Attribute currentCp = getAttribute(ATTRIBUTE_CLASSPATH); + if (currentCp != null) { + for (Enumeration<String> attribEnum = currentCp.getValues(); + attribEnum.hasMoreElements();) { + String value = attribEnum.nextElement(); + classpathAttribute.addValue(value); + } + } + } + storeAttribute(classpathAttribute); + } + + // add in the warnings + Enumeration<String> warnEnum = section.warnings.elements(); + while (warnEnum.hasMoreElements()) { + warnings.addElement(warnEnum.nextElement()); + } + } + + /** + * Write the section out to a print writer without flattening + * multi-values attributes (i.e. Class-Path). + * + * @param writer the Writer to which the section is written + * + * @throws IOException if the section cannot be written + */ + public void write(PrintWriter writer) throws IOException { + write(writer, false); + } + + /** + * Write the section out to a print writer. + * + * @param writer the Writer to which the section is written + * @param flatten whether to collapse multi-valued attributes + * (i.e. potentially Class-Path) Class-Path into a + * single attribute. + * + * @throws IOException if the section cannot be written + * @since Ant 1.8.0 + */ + public void write(PrintWriter writer, boolean flatten) + throws IOException { + if (name != null) { + Attribute nameAttr = new Attribute(ATTRIBUTE_NAME, name); + nameAttr.write(writer); + } + Enumeration<String> e = getAttributeKeys(); + while (e.hasMoreElements()) { + String key = e.nextElement(); + Attribute attribute = getAttribute(key); + attribute.write(writer, flatten); + } + writer.print(EOL); + } + + /** + * Get a attribute of the section + * + * @param attributeName the name of the attribute + * @return a Manifest.Attribute instance if the attribute is + * single-valued, otherwise a Vector of Manifest.Attribute + * instances. + */ + public Attribute getAttribute(String attributeName) { + return attributes.get(attributeName.toLowerCase(Locale.ENGLISH)); + } + + /** + * Get the attribute keys. + * + * @return an Enumeration of Strings, each string being the lower case + * key of an attribute of the section. + */ + public Enumeration<String> getAttributeKeys() { + return CollectionUtils.asEnumeration(attributes.keySet().iterator()); + } + + /** + * Get the value of the attribute with the name given. + * + * @param attributeName the name of the attribute to be returned. + * + * @return the attribute's value or null if the attribute does not exist + * in the section + */ + public String getAttributeValue(String attributeName) { + Attribute attribute = getAttribute(attributeName.toLowerCase(Locale.ENGLISH)); + if (attribute == null) { + return null; + } + return attribute.getValue(); + } + + /** + * Remove the given attribute from the section + * + * @param attributeName the name of the attribute to be removed. + */ + public void removeAttribute(String attributeName) { + String key = attributeName.toLowerCase(Locale.ENGLISH); + attributes.remove(key); + } + + /** + * Add an attribute to the section. + * + * @param attribute the attribute to be added to the section + * + * @exception ManifestException if the attribute is not valid. + */ + public void addConfiguredAttribute(Attribute attribute) + throws ManifestException { + String check = addAttributeAndCheck(attribute); + if (check != null) { + throw new BuildException("Specify the section name using " + + "the \"name\" attribute of the <section> element rather " + + "than using a \"Name\" manifest attribute"); + } + } + + /** + * Add an attribute to the section + * + * @param attribute the attribute to be added. + * + * @return the value of the attribute if it is a name + * attribute - null other wise + * + * @exception ManifestException if the attribute already + * exists in this section. + */ + public String addAttributeAndCheck(Attribute attribute) + throws ManifestException { + if (attribute.getName() == null || attribute.getValue() == null) { + throw new BuildException("Attributes must have name and value"); + } + String attributeKey = attribute.getKey(); + if (attributeKey.equals(ATTRIBUTE_NAME_LC)) { + warnings.addElement("\"" + ATTRIBUTE_NAME + "\" attributes " + + "should not occur in the main section and must be the " + + "first element in all other sections: \"" + + attribute.getName() + ": " + attribute.getValue() + "\""); + return attribute.getValue(); + } + + if (attributeKey.startsWith(ATTRIBUTE_FROM_LC)) { + warnings.addElement(ERROR_FROM_FORBIDDEN + + attribute.getName() + ": " + attribute.getValue() + "\""); + } else { + // classpath attributes go into a vector + if (attributeKey.equals(ATTRIBUTE_CLASSPATH_LC)) { + Attribute classpathAttribute = + attributes.get(attributeKey); + + if (classpathAttribute == null) { + storeAttribute(attribute); + } else { + warnings.addElement("Multiple Class-Path attributes " + + "are supported but violate the Jar " + + "specification and may not be correctly " + + "processed in all environments"); + Enumeration<String> e = attribute.getValues(); + while (e.hasMoreElements()) { + String value = e.nextElement(); + classpathAttribute.addValue(value); + } + } + } else if (attributes.containsKey(attributeKey)) { + throw new ManifestException("The attribute \"" + + attribute.getName() + "\" may not occur more " + + "than once in the same section"); + } else { + storeAttribute(attribute); + } + } + return null; + } + + /** + * Clone this section + * + * @return the cloned Section + * @since Ant 1.5.2 + */ + @Override + public Object clone() { + Section cloned = new Section(); + cloned.setName(name); + Enumeration<String> e = getAttributeKeys(); + while (e.hasMoreElements()) { + String key = e.nextElement(); + Attribute attribute = getAttribute(key); + cloned.storeAttribute(new Attribute(attribute.getName(), + attribute.getValue())); + } + return cloned; + } + + /** + * Store an attribute and update the index. + * + * @param attribute the attribute to be stored + */ + private void storeAttribute(Attribute attribute) { + if (attribute == null) { + return; + } + String attributeKey = attribute.getKey(); + attributes.put(attributeKey, attribute); + } + + /** + * Get the warnings for this section. + * + * @return an Enumeration of warning strings. + */ + public Enumeration<String> getWarnings() { + return warnings.elements(); + } + + /** + * @see java.lang.Object#hashCode + * @return a hash value based on the attributes. + */ + @Override + public int hashCode() { + return attributes.hashCode(); + } + + /** + * @see java.lang.Object#equals + * @param rhs the object to check for equality. + * @return true if the attributes are the same. + */ + @Override + public boolean equals(Object rhs) { + if (rhs == null || rhs.getClass() != getClass()) { + return false; + } + + if (rhs == this) { + return true; + } + + Section rhsSection = (Section) rhs; + + return attributes.equals(rhsSection.attributes); + } + } + + + /** The version of this manifest */ + private String manifestVersion = DEFAULT_MANIFEST_VERSION; + + /** The main section of this manifest */ + private Section mainSection = new Section(); + + /** The named sections of this manifest */ + private Map<String, Section> sections = new LinkedHashMap<String, Section>(); + + /** + * Construct a manifest from Ant's default manifest file. + * + * @return the default manifest. + * @exception BuildException if there is a problem loading the + * default manifest + */ + public static Manifest getDefaultManifest() throws BuildException { + InputStream in = null; + InputStreamReader insr = null; + try { + String defManifest = "/org/apache/tools/ant/defaultManifest.mf"; + in = Manifest.class.getResourceAsStream(defManifest); + if (in == null) { + throw new BuildException("Could not find default manifest: " + + defManifest); + } + try { + insr = new InputStreamReader(in, "UTF-8"); + Manifest defaultManifest = new Manifest(insr); + String version = System.getProperty("java.runtime.version"); + if (version == null) { + version = System.getProperty("java.vm.version"); + } + Attribute createdBy = new Attribute("Created-By", + version + " (" + + System.getProperty("java.vm.vendor") + ")"); + defaultManifest.getMainSection().storeAttribute(createdBy); + return defaultManifest; + } catch (UnsupportedEncodingException e) { + insr = new InputStreamReader(in); + return new Manifest(insr); + } + } catch (ManifestException e) { + throw new BuildException("Default manifest is invalid !!", e); + } catch (IOException e) { + throw new BuildException("Unable to read default manifest", e); + } finally { + FileUtils.close(insr); + FileUtils.close(in); + } + } + + /** Construct an empty manifest */ + public Manifest() { + manifestVersion = null; + } + + /** + * Read a manifest file from the given reader + * + * @param r is the reader from which the Manifest is read + * + * @throws ManifestException if the manifest is not valid according + * to the JAR spec + * @throws IOException if the manifest cannot be read from the reader. + */ + public Manifest(Reader r) throws ManifestException, IOException { + BufferedReader reader = new BufferedReader(r); + // This should be the manifest version + String nextSectionName = mainSection.read(reader); + String readManifestVersion + = mainSection.getAttributeValue(ATTRIBUTE_MANIFEST_VERSION); + if (readManifestVersion != null) { + manifestVersion = readManifestVersion; + mainSection.removeAttribute(ATTRIBUTE_MANIFEST_VERSION); + } + + String line = null; + while ((line = reader.readLine()) != null) { + if (line.length() == 0) { + continue; + } + + Section section = new Section(); + if (nextSectionName == null) { + Attribute sectionName = new Attribute(line); + if (!sectionName.getName().equalsIgnoreCase(ATTRIBUTE_NAME)) { + throw new ManifestException("Manifest sections should " + + "start with a \"" + ATTRIBUTE_NAME + + "\" attribute and not \"" + + sectionName.getName() + "\""); + } + nextSectionName = sectionName.getValue(); + } else { + // we have already started reading this section + // this line is the first attribute. set it and then + // let the normal read handle the rest + Attribute firstAttribute = new Attribute(line); + section.addAttributeAndCheck(firstAttribute); + } + + section.setName(nextSectionName); + nextSectionName = section.read(reader); + addConfiguredSection(section); + } + } + + /** + * Add a section to the manifest + * + * @param section the manifest section to be added + * + * @exception ManifestException if the secti0on is not valid. + */ + public void addConfiguredSection(Section section) + throws ManifestException { + String sectionName = section.getName(); + if (sectionName == null) { + throw new BuildException("Sections must have a name"); + } + sections.put(sectionName, section); + } + + /** + * Add an attribute to the manifest - it is added to the main section. + * + * @param attribute the attribute to be added. + * + * @exception ManifestException if the attribute is not valid. + */ + public void addConfiguredAttribute(Attribute attribute) + throws ManifestException { + if (attribute.getKey() == null || attribute.getValue() == null) { + throw new BuildException("Attributes must have name and value"); + } + if (attribute.getKey().equals(ATTRIBUTE_MANIFEST_VERSION_LC)) { + manifestVersion = attribute.getValue(); + } else { + mainSection.addConfiguredAttribute(attribute); + } + } + + /** + * Merge the contents of the given manifest into this manifest + * without merging Class-Path attributes. + * + * @param other the Manifest to be merged with this one. + * + * @throws ManifestException if there is a problem merging the + * manifest according to the Manifest spec. + */ + public void merge(Manifest other) throws ManifestException { + merge(other, false); + } + + /** + * Merge the contents of the given manifest into this manifest + * without merging Class-Path attributes. + * + * @param other the Manifest to be merged with this one. + * @param overwriteMain whether to overwrite the main section + * of the current manifest + * + * @throws ManifestException if there is a problem merging the + * manifest according to the Manifest spec. + */ + public void merge(Manifest other, boolean overwriteMain) + throws ManifestException { + merge(other, overwriteMain, false); + } + + /** + * Merge the contents of the given manifest into this manifest + * + * @param other the Manifest to be merged with this one. + * @param overwriteMain whether to overwrite the main section + * of the current manifest + * @param mergeClassPaths whether Class-Path attributes should be + * merged. + * + * @throws ManifestException if there is a problem merging the + * manifest according to the Manifest spec. + * + * @since Ant 1.8.0 + */ + public void merge(Manifest other, boolean overwriteMain, + boolean mergeClassPaths) + throws ManifestException { + if (other != null) { + if (overwriteMain) { + mainSection = (Section) other.mainSection.clone(); + } else { + mainSection.merge(other.mainSection, mergeClassPaths); + } + + if (other.manifestVersion != null) { + manifestVersion = other.manifestVersion; + } + + Enumeration<String> e = other.getSectionNames(); + while (e.hasMoreElements()) { + String sectionName = e.nextElement(); + Section ourSection = sections.get(sectionName); + Section otherSection + = other.sections.get(sectionName); + if (ourSection == null) { + if (otherSection != null) { + addConfiguredSection((Section) otherSection.clone()); + } + } else { + ourSection.merge(otherSection, mergeClassPaths); + } + } + } + } + + /** + * Write the manifest out to a print writer without flattening + * multi-values attributes (i.e. Class-Path). + * + * @param writer the Writer to which the manifest is written + * + * @throws IOException if the manifest cannot be written + */ + public void write(PrintWriter writer) throws IOException { + write(writer, false); + } + + /** + * Write the manifest out to a print writer. + * + * @param writer the Writer to which the manifest is written + * @param flatten whether to collapse multi-valued attributes + * (i.e. potentially Class-Path) Class-Path into a single + * attribute. + * + * @throws IOException if the manifest cannot be written + * @since Ant 1.8.0 + */ + public void write(PrintWriter writer, boolean flatten) throws IOException { + writer.print(ATTRIBUTE_MANIFEST_VERSION + ": " + manifestVersion + EOL); + String signatureVersion + = mainSection.getAttributeValue(ATTRIBUTE_SIGNATURE_VERSION); + if (signatureVersion != null) { + writer.print(ATTRIBUTE_SIGNATURE_VERSION + ": " + + signatureVersion + EOL); + mainSection.removeAttribute(ATTRIBUTE_SIGNATURE_VERSION); + } + mainSection.write(writer, flatten); + + // add it back + if (signatureVersion != null) { + try { + Attribute svAttr = new Attribute(ATTRIBUTE_SIGNATURE_VERSION, + signatureVersion); + mainSection.addConfiguredAttribute(svAttr); + } catch (ManifestException e) { + // shouldn't happen - ignore + } + } + + for (String sectionName : sections.keySet()) { + Section section = getSection(sectionName); + section.write(writer, flatten); + } + } + + /** + * Convert the manifest to its string representation + * + * @return a multiline string with the Manifest as it + * appears in a Manifest file. + */ + @Override + public String toString() { + StringWriter sw = new StringWriter(); + try { + write(new PrintWriter(sw)); + } catch (IOException e) { + return null; + } + return sw.toString(); + } + + /** + * Get the warnings for this manifest. + * + * @return an enumeration of warning strings + */ + public Enumeration<String> getWarnings() { + Vector<String> warnings = new Vector<String>(); + + Enumeration<String> warnEnum = mainSection.getWarnings(); + while (warnEnum.hasMoreElements()) { + warnings.addElement(warnEnum.nextElement()); + } + + // create a vector and add in the warnings for all the sections + for (Section section : sections.values()) { + Enumeration<String> e2 = section.getWarnings(); + while (e2.hasMoreElements()) { + warnings.addElement(e2.nextElement()); + } + } + + return warnings.elements(); + } + + /** + * @see java.lang.Object#hashCode + * @return a hashcode based on the version, main and sections. + */ + @Override + public int hashCode() { + int hashCode = 0; + + if (manifestVersion != null) { + hashCode += manifestVersion.hashCode(); + } + hashCode += mainSection.hashCode(); + hashCode += sections.hashCode(); + + return hashCode; + } + + /** + * @see java.lang.Object#equals + * @param rhs the object to check for equality. + * @return true if the version, main and sections are the same. + */ + @Override + public boolean equals(Object rhs) { + if (rhs == null || rhs.getClass() != getClass()) { + return false; + } + + if (rhs == this) { + return true; + } + + Manifest rhsManifest = (Manifest) rhs; + if (manifestVersion == null) { + if (rhsManifest.manifestVersion != null) { + return false; + } + } else if (!manifestVersion.equals(rhsManifest.manifestVersion)) { + return false; + } + + if (!mainSection.equals(rhsManifest.mainSection)) { + return false; + } + + return sections.equals(rhsManifest.sections); + } + + /** + * Get the version of the manifest + * + * @return the manifest's version string + */ + public String getManifestVersion() { + return manifestVersion; + } + + /** + * Get the main section of the manifest + * + * @return the main section of the manifest + */ + public Section getMainSection() { + return mainSection; + } + + /** + * Get a particular section from the manifest + * + * @param name the name of the section desired. + * @return the specified section or null if that section + * does not exist in the manifest + */ + public Section getSection(String name) { + return sections.get(name); + } + + /** + * Get the section names in this manifest. + * + * @return an Enumeration of section names + */ + public Enumeration<String> getSectionNames() { + return CollectionUtils.asEnumeration(sections.keySet().iterator()); + } +} |