| /* |
| * 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()); |
| } |
| } |