0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant.taskdefs;
0020:
0021: import java.io.BufferedReader;
0022: import java.io.IOException;
0023: import java.io.InputStream;
0024: import java.io.InputStreamReader;
0025: import java.io.PrintWriter;
0026: import java.io.Reader;
0027: import java.io.StringWriter;
0028: import java.io.UnsupportedEncodingException;
0029: import java.util.Enumeration;
0030: import java.util.Hashtable;
0031: import java.util.Vector;
0032: import org.apache.tools.ant.BuildException;
0033: import org.apache.tools.ant.util.FileUtils;
0034:
0035: /**
0036: * Holds the data of a jar manifest.
0037: *
0038: * Manifests are processed according to the
0039: * {@link <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html">Jar
0040: * file specification.</a>}.
0041: * Specifically, a manifest element consists of
0042: * a set of attributes and sections. These sections in turn may contain
0043: * attributes. Note in particular that this may result in manifest lines
0044: * greater than 72 bytes being wrapped and continued on the next
0045: * line. If an application can not handle the continuation mechanism, it
0046: * is a defect in the application, not this task.
0047: *
0048: *
0049: * @since Ant 1.4
0050: */
0051: public class Manifest {
0052: /** The standard manifest version header */
0053: public static final String ATTRIBUTE_MANIFEST_VERSION = "Manifest-Version";
0054:
0055: /** The standard Signature Version header */
0056: public static final String ATTRIBUTE_SIGNATURE_VERSION = "Signature-Version";
0057:
0058: /** The Name Attribute is the first in a named section */
0059: public static final String ATTRIBUTE_NAME = "Name";
0060:
0061: /** The From Header is disallowed in a Manifest */
0062: public static final String ATTRIBUTE_FROM = "From";
0063:
0064: /** The Class-Path Header is special - it can be duplicated */
0065: public static final String ATTRIBUTE_CLASSPATH = "Class-Path";
0066:
0067: /** Default Manifest version if one is not specified */
0068: public static final String DEFAULT_MANIFEST_VERSION = "1.0";
0069:
0070: /** The max length of a line in a Manifest */
0071: public static final int MAX_LINE_LENGTH = 72;
0072:
0073: /**
0074: * Max length of a line section which is continued. Need to allow
0075: * for the CRLF.
0076: */
0077: public static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2;
0078:
0079: /** The End-Of-Line marker in manifests */
0080: public static final String EOL = "\r\n";
0081: /** Error for attributes */
0082: public static final String ERROR_FROM_FORBIDDEN = "Manifest attributes should not start "
0083: + "with \"" + ATTRIBUTE_FROM + "\" in \"";
0084:
0085: /** Encoding to be used for JAR files. */
0086: public static final String JAR_ENCODING = "UTF-8";
0087:
0088: /**
0089: * An attribute for the manifest.
0090: * Those attributes that are not nested into a section will be added to the "Main" section.
0091: */
0092: public static class Attribute {
0093:
0094: /**
0095: * Maximum length of the name to have the value starting on the same
0096: * line as the name. This to stay under 72 bytes total line length
0097: * (including CRLF).
0098: */
0099: private static final int MAX_NAME_VALUE_LENGTH = 68;
0100:
0101: /**
0102: * Maximum length of the name according to the jar specification.
0103: * In this case the first line will have 74 bytes total line length
0104: * (including CRLF). This conflicts with the 72 bytes total line length
0105: * max, but is the only possible conclusion from the manifest specification, if
0106: * names with 70 bytes length are allowed, have to be on the first line, and
0107: * have to be followed by ": ".
0108: */
0109: private static final int MAX_NAME_LENGTH = 70;
0110:
0111: /** The attribute's name */
0112: private String name = null;
0113:
0114: /** The attribute's value */
0115: private Vector values = new Vector();
0116:
0117: /**
0118: * For multivalued attributes, this is the index of the attribute
0119: * currently being defined.
0120: */
0121: private int currentIndex = 0;
0122:
0123: /**
0124: * Construct an empty attribute */
0125: public Attribute() {
0126: }
0127:
0128: /**
0129: * Construct an attribute by parsing a line from the Manifest
0130: *
0131: * @param line the line containing the attribute name and value
0132: *
0133: * @throws ManifestException if the line is not valid
0134: */
0135: public Attribute(String line) throws ManifestException {
0136: parse(line);
0137: }
0138:
0139: /**
0140: * Construct a manifest by specifying its name and value
0141: *
0142: * @param name the attribute's name
0143: * @param value the Attribute's value
0144: */
0145: public Attribute(String name, String value) {
0146: this .name = name;
0147: setValue(value);
0148: }
0149:
0150: /**
0151: * @see java.lang.Object#hashCode
0152: * @return a hashcode based on the key and values.
0153: */
0154: public int hashCode() {
0155: int hashCode = 0;
0156:
0157: if (name != null) {
0158: hashCode += getKey().hashCode();
0159: }
0160:
0161: hashCode += values.hashCode();
0162: return hashCode;
0163: }
0164:
0165: /**
0166: * @param rhs the object to check for equality.
0167: * @see java.lang.Object#equals
0168: * @return true if the key and values are the same.
0169: */
0170: public boolean equals(Object rhs) {
0171: if (rhs == null || rhs.getClass() != getClass()) {
0172: return false;
0173: }
0174:
0175: if (rhs == this ) {
0176: return true;
0177: }
0178:
0179: Attribute rhsAttribute = (Attribute) rhs;
0180: String lhsKey = getKey();
0181: String rhsKey = rhsAttribute.getKey();
0182: if ((lhsKey == null && rhsKey != null)
0183: || (lhsKey != null && rhsKey == null)
0184: || !lhsKey.equals(rhsKey)) {
0185: return false;
0186: }
0187:
0188: return values.equals(rhsAttribute.values);
0189: }
0190:
0191: /**
0192: * Parse a line into name and value pairs
0193: *
0194: * @param line the line to be parsed
0195: *
0196: * @throws ManifestException if the line does not contain a colon
0197: * separating the name and value
0198: */
0199: public void parse(String line) throws ManifestException {
0200: int index = line.indexOf(": ");
0201: if (index == -1) {
0202: throw new ManifestException(
0203: "Manifest line \""
0204: + line
0205: + "\" is not valid as it does not "
0206: + "contain a name and a value separated by ': ' ");
0207: }
0208: name = line.substring(0, index);
0209: setValue(line.substring(index + 2));
0210: }
0211:
0212: /**
0213: * Set the Attribute's name; required
0214: *
0215: * @param name the attribute's name
0216: */
0217: public void setName(String name) {
0218: this .name = name;
0219: }
0220:
0221: /**
0222: * Get the Attribute's name
0223: *
0224: * @return the attribute's name.
0225: */
0226: public String getName() {
0227: return name;
0228: }
0229:
0230: /**
0231: * Get the attribute's Key - its name in lower case.
0232: *
0233: * @return the attribute's key.
0234: */
0235: public String getKey() {
0236: if (name == null) {
0237: return null;
0238: }
0239: return name.toLowerCase();
0240: }
0241:
0242: /**
0243: * Set the Attribute's value; required
0244: *
0245: * @param value the attribute's value
0246: */
0247: public void setValue(String value) {
0248: if (currentIndex >= values.size()) {
0249: values.addElement(value);
0250: currentIndex = values.size() - 1;
0251: } else {
0252: values.setElementAt(value, currentIndex);
0253: }
0254: }
0255:
0256: /**
0257: * Get the Attribute's value.
0258: *
0259: * @return the attribute's value.
0260: */
0261: public String getValue() {
0262: if (values.size() == 0) {
0263: return null;
0264: }
0265:
0266: String fullValue = "";
0267: for (Enumeration e = getValues(); e.hasMoreElements();) {
0268: String value = (String) e.nextElement();
0269: fullValue += value + " ";
0270: }
0271: return fullValue.trim();
0272: }
0273:
0274: /**
0275: * Add a new value to this attribute - making it multivalued.
0276: *
0277: * @param value the attribute's additional value
0278: */
0279: public void addValue(String value) {
0280: currentIndex++;
0281: setValue(value);
0282: }
0283:
0284: /**
0285: * Get all the attribute's values.
0286: *
0287: * @return an enumeration of the attributes values
0288: */
0289: public Enumeration getValues() {
0290: return values.elements();
0291: }
0292:
0293: /**
0294: * Add a continuation line from the Manifest file.
0295: *
0296: * When lines are too long in a manifest, they are continued on the
0297: * next line by starting with a space. This method adds the continuation
0298: * data to the attribute value by skipping the first character.
0299: *
0300: * @param line the continuation line.
0301: */
0302: public void addContinuation(String line) {
0303: String currentValue = (String) values
0304: .elementAt(currentIndex);
0305: setValue(currentValue + line.substring(1));
0306: }
0307:
0308: /**
0309: * Write the attribute out to a print writer.
0310: *
0311: * @param writer the Writer to which the attribute is written
0312: *
0313: * @throws IOException if the attribute value cannot be written
0314: */
0315: public void write(PrintWriter writer) throws IOException {
0316: for (Enumeration e = getValues(); e.hasMoreElements();) {
0317: writeValue(writer, (String) e.nextElement());
0318: }
0319: }
0320:
0321: /**
0322: * Write a single attribute value out
0323: *
0324: * @param writer the Writer to which the attribute is written
0325: * @param value the attribute value
0326: *
0327: * @throws IOException if the attribute value cannot be written
0328: */
0329: private void writeValue(PrintWriter writer, String value)
0330: throws IOException {
0331: String line = null;
0332: int nameLength = name.getBytes(JAR_ENCODING).length;
0333: if (nameLength > MAX_NAME_VALUE_LENGTH) {
0334: if (nameLength > MAX_NAME_LENGTH) {
0335: throw new IOException(
0336: "Unable to write manifest line " + name
0337: + ": " + value);
0338: }
0339: writer.print(name + ": " + EOL);
0340: line = " " + value;
0341: } else {
0342: line = name + ": " + value;
0343: }
0344: while (line.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH) {
0345: // try to find a MAX_LINE_LENGTH byte section
0346: int breakIndex = MAX_SECTION_LENGTH;
0347: if (breakIndex >= line.length()) {
0348: breakIndex = line.length() - 1;
0349: }
0350: String section = line.substring(0, breakIndex);
0351: while (section.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH
0352: && breakIndex > 0) {
0353: breakIndex--;
0354: section = line.substring(0, breakIndex);
0355: }
0356: if (breakIndex == 0) {
0357: throw new IOException(
0358: "Unable to write manifest line " + name
0359: + ": " + value);
0360: }
0361: writer.print(section + EOL);
0362: line = " " + line.substring(breakIndex);
0363: }
0364: writer.print(line + EOL);
0365: }
0366: }
0367:
0368: /**
0369: * A manifest section - you can nest attribute elements into sections.
0370: * A section consists of a set of attribute values,
0371: * separated from other sections by a blank line.
0372: */
0373: public static class Section {
0374: /** Warnings for this section */
0375: private Vector warnings = new Vector();
0376:
0377: /**
0378: * The section's name if any. The main section in a
0379: * manifest is unnamed.
0380: */
0381: private String name = null;
0382:
0383: /** The section's attributes.*/
0384: private Hashtable attributes = new Hashtable();
0385:
0386: /** Index used to retain the attribute ordering */
0387: private Vector attributeIndex = new Vector();
0388:
0389: /**
0390: * The name of the section; optional -default is the main section.
0391: * @param name the section's name
0392: */
0393: public void setName(String name) {
0394: this .name = name;
0395: }
0396:
0397: /**
0398: * Get the Section's name.
0399: *
0400: * @return the section's name.
0401: */
0402: public String getName() {
0403: return name;
0404: }
0405:
0406: /**
0407: * Read a section through a reader.
0408: *
0409: * @param reader the reader from which the section is read
0410: *
0411: * @return the name of the next section if it has been read as
0412: * part of this section - This only happens if the
0413: * Manifest is malformed.
0414: *
0415: * @throws ManifestException if the section is not valid according
0416: * to the JAR spec
0417: * @throws IOException if the section cannot be read from the reader.
0418: */
0419: public String read(BufferedReader reader)
0420: throws ManifestException, IOException {
0421: Attribute attribute = null;
0422: while (true) {
0423: String line = reader.readLine();
0424: if (line == null || line.length() == 0) {
0425: return null;
0426: }
0427: if (line.charAt(0) == ' ') {
0428: // continuation line
0429: if (attribute == null) {
0430: if (name != null) {
0431: // a continuation on the first line is a
0432: // continuation of the name - concatenate this
0433: // line and the name
0434: name += line.substring(1);
0435: } else {
0436: throw new ManifestException(
0437: "Can't start an "
0438: + "attribute with a continuation line "
0439: + line);
0440: }
0441: } else {
0442: attribute.addContinuation(line);
0443: }
0444: } else {
0445: attribute = new Attribute(line);
0446: String nameReadAhead = addAttributeAndCheck(attribute);
0447: // refresh attribute in case of multivalued attributes.
0448: attribute = getAttribute(attribute.getKey());
0449: if (nameReadAhead != null) {
0450: return nameReadAhead;
0451: }
0452: }
0453: }
0454: }
0455:
0456: /**
0457: * Merge in another section
0458: *
0459: * @param section the section to be merged with this one.
0460: *
0461: * @throws ManifestException if the sections cannot be merged.
0462: */
0463: public void merge(Section section) throws ManifestException {
0464: if (name == null && section.getName() != null
0465: || name != null
0466: && !(name.equalsIgnoreCase(section.getName()))) {
0467: throw new ManifestException("Unable to merge sections "
0468: + "with different names");
0469: }
0470:
0471: Enumeration e = section.getAttributeKeys();
0472: Attribute classpathAttribute = null;
0473: while (e.hasMoreElements()) {
0474: String attributeName = (String) e.nextElement();
0475: Attribute attribute = section
0476: .getAttribute(attributeName);
0477: if (attributeName.equalsIgnoreCase(ATTRIBUTE_CLASSPATH)) {
0478: if (classpathAttribute == null) {
0479: classpathAttribute = new Attribute();
0480: classpathAttribute.setName(ATTRIBUTE_CLASSPATH);
0481: }
0482: Enumeration cpe = attribute.getValues();
0483: while (cpe.hasMoreElements()) {
0484: String value = (String) cpe.nextElement();
0485: classpathAttribute.addValue(value);
0486: }
0487: } else {
0488: // the merge file always wins
0489: storeAttribute(attribute);
0490: }
0491: }
0492:
0493: if (classpathAttribute != null) {
0494: // the merge file *always* wins, even for Class-Path
0495: storeAttribute(classpathAttribute);
0496: }
0497:
0498: // add in the warnings
0499: Enumeration warnEnum = section.warnings.elements();
0500: while (warnEnum.hasMoreElements()) {
0501: warnings.addElement(warnEnum.nextElement());
0502: }
0503: }
0504:
0505: /**
0506: * Write the section out to a print writer.
0507: *
0508: * @param writer the Writer to which the section is written
0509: *
0510: * @throws IOException if the section cannot be written
0511: */
0512: public void write(PrintWriter writer) throws IOException {
0513: if (name != null) {
0514: Attribute nameAttr = new Attribute(ATTRIBUTE_NAME, name);
0515: nameAttr.write(writer);
0516: }
0517: Enumeration e = getAttributeKeys();
0518: while (e.hasMoreElements()) {
0519: String key = (String) e.nextElement();
0520: Attribute attribute = getAttribute(key);
0521: attribute.write(writer);
0522: }
0523: writer.print(EOL);
0524: }
0525:
0526: /**
0527: * Get a attribute of the section
0528: *
0529: * @param attributeName the name of the attribute
0530: * @return a Manifest.Attribute instance if the attribute is
0531: * single-valued, otherwise a Vector of Manifest.Attribute
0532: * instances.
0533: */
0534: public Attribute getAttribute(String attributeName) {
0535: return (Attribute) attributes.get(attributeName
0536: .toLowerCase());
0537: }
0538:
0539: /**
0540: * Get the attribute keys.
0541: *
0542: * @return an Enumeration of Strings, each string being the lower case
0543: * key of an attribute of the section.
0544: */
0545: public Enumeration getAttributeKeys() {
0546: return attributeIndex.elements();
0547: }
0548:
0549: /**
0550: * Get the value of the attribute with the name given.
0551: *
0552: * @param attributeName the name of the attribute to be returned.
0553: *
0554: * @return the attribute's value or null if the attribute does not exist
0555: * in the section
0556: */
0557: public String getAttributeValue(String attributeName) {
0558: Attribute attribute = getAttribute(attributeName
0559: .toLowerCase());
0560: if (attribute == null) {
0561: return null;
0562: }
0563: return attribute.getValue();
0564: }
0565:
0566: /**
0567: * Remove the given attribute from the section
0568: *
0569: * @param attributeName the name of the attribute to be removed.
0570: */
0571: public void removeAttribute(String attributeName) {
0572: String key = attributeName.toLowerCase();
0573: attributes.remove(key);
0574: attributeIndex.removeElement(key);
0575: }
0576:
0577: /**
0578: * Add an attribute to the section.
0579: *
0580: * @param attribute the attribute to be added to the section
0581: *
0582: * @exception ManifestException if the attribute is not valid.
0583: */
0584: public void addConfiguredAttribute(Attribute attribute)
0585: throws ManifestException {
0586: String check = addAttributeAndCheck(attribute);
0587: if (check != null) {
0588: throw new BuildException(
0589: "Specify the section name using "
0590: + "the \"name\" attribute of the <section> element rather "
0591: + "than using a \"Name\" manifest attribute");
0592: }
0593: }
0594:
0595: /**
0596: * Add an attribute to the section
0597: *
0598: * @param attribute the attribute to be added.
0599: *
0600: * @return the value of the attribute if it is a name
0601: * attribute - null other wise
0602: *
0603: * @exception ManifestException if the attribute already
0604: * exists in this section.
0605: */
0606: public String addAttributeAndCheck(Attribute attribute)
0607: throws ManifestException {
0608: if (attribute.getName() == null
0609: || attribute.getValue() == null) {
0610: throw new BuildException(
0611: "Attributes must have name and value");
0612: }
0613: if (attribute.getKey().equalsIgnoreCase(ATTRIBUTE_NAME)) {
0614: warnings
0615: .addElement("\""
0616: + ATTRIBUTE_NAME
0617: + "\" attributes "
0618: + "should not occur in the main section and must be the "
0619: + "first element in all other sections: \""
0620: + attribute.getName() + ": "
0621: + attribute.getValue() + "\"");
0622: return attribute.getValue();
0623: }
0624:
0625: if (attribute.getKey().startsWith(
0626: ATTRIBUTE_FROM.toLowerCase())) {
0627: warnings.addElement(ERROR_FROM_FORBIDDEN
0628: + attribute.getName() + ": "
0629: + attribute.getValue() + "\"");
0630: } else {
0631: // classpath attributes go into a vector
0632: String attributeKey = attribute.getKey();
0633: if (attributeKey.equalsIgnoreCase(ATTRIBUTE_CLASSPATH)) {
0634: Attribute classpathAttribute = (Attribute) attributes
0635: .get(attributeKey);
0636:
0637: if (classpathAttribute == null) {
0638: storeAttribute(attribute);
0639: } else {
0640: warnings
0641: .addElement("Multiple Class-Path attributes "
0642: + "are supported but violate the Jar "
0643: + "specification and may not be correctly "
0644: + "processed in all environments");
0645: Enumeration e = attribute.getValues();
0646: while (e.hasMoreElements()) {
0647: String value = (String) e.nextElement();
0648: classpathAttribute.addValue(value);
0649: }
0650: }
0651: } else if (attributes.containsKey(attributeKey)) {
0652: throw new ManifestException("The attribute \""
0653: + attribute.getName()
0654: + "\" may not occur more "
0655: + "than once in the same section");
0656: } else {
0657: storeAttribute(attribute);
0658: }
0659: }
0660: return null;
0661: }
0662:
0663: /**
0664: * Clone this section
0665: *
0666: * @return the cloned Section
0667: * @since Ant 1.5.2
0668: */
0669: public Object clone() {
0670: Section cloned = new Section();
0671: cloned.setName(name);
0672: Enumeration e = getAttributeKeys();
0673: while (e.hasMoreElements()) {
0674: String key = (String) e.nextElement();
0675: Attribute attribute = getAttribute(key);
0676: cloned.storeAttribute(new Attribute(
0677: attribute.getName(), attribute.getValue()));
0678: }
0679: return cloned;
0680: }
0681:
0682: /**
0683: * Store an attribute and update the index.
0684: *
0685: * @param attribute the attribute to be stored
0686: */
0687: private void storeAttribute(Attribute attribute) {
0688: if (attribute == null) {
0689: return;
0690: }
0691: String attributeKey = attribute.getKey();
0692: attributes.put(attributeKey, attribute);
0693: if (!attributeIndex.contains(attributeKey)) {
0694: attributeIndex.addElement(attributeKey);
0695: }
0696: }
0697:
0698: /**
0699: * Get the warnings for this section.
0700: *
0701: * @return an Enumeration of warning strings.
0702: */
0703: public Enumeration getWarnings() {
0704: return warnings.elements();
0705: }
0706:
0707: /**
0708: * @see java.lang.Object#hashCode
0709: * @return a hash value based on the attributes.
0710: */
0711: public int hashCode() {
0712: return attributes.hashCode();
0713: }
0714:
0715: /**
0716: * @see java.lang.Object#equals
0717: * @param rhs the object to check for equality.
0718: * @return true if the attributes are the same.
0719: */
0720: public boolean equals(Object rhs) {
0721: if (rhs == null || rhs.getClass() != getClass()) {
0722: return false;
0723: }
0724:
0725: if (rhs == this ) {
0726: return true;
0727: }
0728:
0729: Section rhsSection = (Section) rhs;
0730:
0731: return attributes.equals(rhsSection.attributes);
0732: }
0733: }
0734:
0735: /** The version of this manifest */
0736: private String manifestVersion = DEFAULT_MANIFEST_VERSION;
0737:
0738: /** The main section of this manifest */
0739: private Section mainSection = new Section();
0740:
0741: /** The named sections of this manifest */
0742: private Hashtable sections = new Hashtable();
0743:
0744: /** Index of sections - used to retain order of sections in manifest */
0745: private Vector sectionIndex = new Vector();
0746:
0747: /**
0748: * Construct a manifest from Ant's default manifest file.
0749: *
0750: * @return the default manifest.
0751: * @exception BuildException if there is a problem loading the
0752: * default manifest
0753: */
0754: public static Manifest getDefaultManifest() throws BuildException {
0755: InputStream in = null;
0756: InputStreamReader insr = null;
0757: try {
0758: String defManifest = "/org/apache/tools/ant/defaultManifest.mf";
0759: in = Manifest.class.getResourceAsStream(defManifest);
0760: if (in == null) {
0761: throw new BuildException(
0762: "Could not find default manifest: "
0763: + defManifest);
0764: }
0765: try {
0766: insr = new InputStreamReader(in, "UTF-8");
0767: Manifest defaultManifest = new Manifest(insr);
0768: Attribute createdBy = new Attribute("Created-By",
0769: System.getProperty("java.vm.version") + " ("
0770: + System.getProperty("java.vm.vendor")
0771: + ")");
0772: defaultManifest.getMainSection().storeAttribute(
0773: createdBy);
0774: return defaultManifest;
0775: } catch (UnsupportedEncodingException e) {
0776: insr = new InputStreamReader(in);
0777: return new Manifest(insr);
0778: }
0779: } catch (ManifestException e) {
0780: throw new BuildException("Default manifest is invalid !!",
0781: e);
0782: } catch (IOException e) {
0783: throw new BuildException("Unable to read default manifest",
0784: e);
0785: } finally {
0786: FileUtils.close(insr);
0787: FileUtils.close(in);
0788: }
0789: }
0790:
0791: /** Construct an empty manifest */
0792: public Manifest() {
0793: manifestVersion = null;
0794: }
0795:
0796: /**
0797: * Read a manifest file from the given reader
0798: *
0799: * @param r is the reader from which the Manifest is read
0800: *
0801: * @throws ManifestException if the manifest is not valid according
0802: * to the JAR spec
0803: * @throws IOException if the manifest cannot be read from the reader.
0804: */
0805: public Manifest(Reader r) throws ManifestException, IOException {
0806: BufferedReader reader = new BufferedReader(r);
0807: // This should be the manifest version
0808: String nextSectionName = mainSection.read(reader);
0809: String readManifestVersion = mainSection
0810: .getAttributeValue(ATTRIBUTE_MANIFEST_VERSION);
0811: if (readManifestVersion != null) {
0812: manifestVersion = readManifestVersion;
0813: mainSection.removeAttribute(ATTRIBUTE_MANIFEST_VERSION);
0814: }
0815:
0816: String line = null;
0817: while ((line = reader.readLine()) != null) {
0818: if (line.length() == 0) {
0819: continue;
0820: }
0821:
0822: Section section = new Section();
0823: if (nextSectionName == null) {
0824: Attribute sectionName = new Attribute(line);
0825: if (!sectionName.getName().equalsIgnoreCase(
0826: ATTRIBUTE_NAME)) {
0827: throw new ManifestException(
0828: "Manifest sections should "
0829: + "start with a \""
0830: + ATTRIBUTE_NAME
0831: + "\" attribute and not \""
0832: + sectionName.getName() + "\"");
0833: }
0834: nextSectionName = sectionName.getValue();
0835: } else {
0836: // we have already started reading this section
0837: // this line is the first attribute. set it and then
0838: // let the normal read handle the rest
0839: Attribute firstAttribute = new Attribute(line);
0840: section.addAttributeAndCheck(firstAttribute);
0841: }
0842:
0843: section.setName(nextSectionName);
0844: nextSectionName = section.read(reader);
0845: addConfiguredSection(section);
0846: }
0847: }
0848:
0849: /**
0850: * Add a section to the manifest
0851: *
0852: * @param section the manifest section to be added
0853: *
0854: * @exception ManifestException if the secti0on is not valid.
0855: */
0856: public void addConfiguredSection(Section section)
0857: throws ManifestException {
0858: String sectionName = section.getName();
0859: if (sectionName == null) {
0860: throw new BuildException("Sections must have a name");
0861: }
0862: sections.put(sectionName, section);
0863: if (!sectionIndex.contains(sectionName)) {
0864: sectionIndex.addElement(sectionName);
0865: }
0866: }
0867:
0868: /**
0869: * Add an attribute to the manifest - it is added to the main section.
0870: *
0871: * @param attribute the attribute to be added.
0872: *
0873: * @exception ManifestException if the attribute is not valid.
0874: */
0875: public void addConfiguredAttribute(Attribute attribute)
0876: throws ManifestException {
0877: if (attribute.getKey() == null || attribute.getValue() == null) {
0878: throw new BuildException(
0879: "Attributes must have name and value");
0880: }
0881: if (attribute.getKey().equalsIgnoreCase(
0882: ATTRIBUTE_MANIFEST_VERSION)) {
0883: manifestVersion = attribute.getValue();
0884: } else {
0885: mainSection.addConfiguredAttribute(attribute);
0886: }
0887: }
0888:
0889: /**
0890: * Merge the contents of the given manifest into this manifest
0891: *
0892: * @param other the Manifest to be merged with this one.
0893: *
0894: * @throws ManifestException if there is a problem merging the
0895: * manifest according to the Manifest spec.
0896: */
0897: public void merge(Manifest other) throws ManifestException {
0898: merge(other, false);
0899: }
0900:
0901: /**
0902: * Merge the contents of the given manifest into this manifest
0903: *
0904: * @param other the Manifest to be merged with this one.
0905: * @param overwriteMain whether to overwrite the main section
0906: * of the current manifest
0907: *
0908: * @throws ManifestException if there is a problem merging the
0909: * manifest according to the Manifest spec.
0910: */
0911: public void merge(Manifest other, boolean overwriteMain)
0912: throws ManifestException {
0913: if (other != null) {
0914: if (overwriteMain) {
0915: mainSection = (Section) other.mainSection.clone();
0916: } else {
0917: mainSection.merge(other.mainSection);
0918: }
0919:
0920: if (other.manifestVersion != null) {
0921: manifestVersion = other.manifestVersion;
0922: }
0923:
0924: Enumeration e = other.getSectionNames();
0925: while (e.hasMoreElements()) {
0926: String sectionName = (String) e.nextElement();
0927: Section ourSection = (Section) sections
0928: .get(sectionName);
0929: Section otherSection = (Section) other.sections
0930: .get(sectionName);
0931: if (ourSection == null) {
0932: if (otherSection != null) {
0933: addConfiguredSection((Section) otherSection
0934: .clone());
0935: }
0936: } else {
0937: ourSection.merge(otherSection);
0938: }
0939: }
0940: }
0941: }
0942:
0943: /**
0944: * Write the manifest out to a print writer.
0945: *
0946: * @param writer the Writer to which the manifest is written
0947: *
0948: * @throws IOException if the manifest cannot be written
0949: */
0950: public void write(PrintWriter writer) throws IOException {
0951: writer.print(ATTRIBUTE_MANIFEST_VERSION + ": "
0952: + manifestVersion + EOL);
0953: String signatureVersion = mainSection
0954: .getAttributeValue(ATTRIBUTE_SIGNATURE_VERSION);
0955: if (signatureVersion != null) {
0956: writer.print(ATTRIBUTE_SIGNATURE_VERSION + ": "
0957: + signatureVersion + EOL);
0958: mainSection.removeAttribute(ATTRIBUTE_SIGNATURE_VERSION);
0959: }
0960: mainSection.write(writer);
0961:
0962: // add it back
0963: if (signatureVersion != null) {
0964: try {
0965: Attribute svAttr = new Attribute(
0966: ATTRIBUTE_SIGNATURE_VERSION, signatureVersion);
0967: mainSection.addConfiguredAttribute(svAttr);
0968: } catch (ManifestException e) {
0969: // shouldn't happen - ignore
0970: }
0971: }
0972:
0973: Enumeration e = sectionIndex.elements();
0974: while (e.hasMoreElements()) {
0975: String sectionName = (String) e.nextElement();
0976: Section section = getSection(sectionName);
0977: section.write(writer);
0978: }
0979: }
0980:
0981: /**
0982: * Convert the manifest to its string representation
0983: *
0984: * @return a multiline string with the Manifest as it
0985: * appears in a Manifest file.
0986: */
0987: public String toString() {
0988: StringWriter sw = new StringWriter();
0989: try {
0990: write(new PrintWriter(sw));
0991: } catch (IOException e) {
0992: return null;
0993: }
0994: return sw.toString();
0995: }
0996:
0997: /**
0998: * Get the warnings for this manifest.
0999: *
1000: * @return an enumeration of warning strings
1001: */
1002: public Enumeration getWarnings() {
1003: Vector warnings = new Vector();
1004:
1005: Enumeration warnEnum = mainSection.getWarnings();
1006: while (warnEnum.hasMoreElements()) {
1007: warnings.addElement(warnEnum.nextElement());
1008: }
1009:
1010: // create a vector and add in the warnings for all the sections
1011: Enumeration e = sections.elements();
1012: while (e.hasMoreElements()) {
1013: Section section = (Section) e.nextElement();
1014: Enumeration e2 = section.getWarnings();
1015: while (e2.hasMoreElements()) {
1016: warnings.addElement(e2.nextElement());
1017: }
1018: }
1019:
1020: return warnings.elements();
1021: }
1022:
1023: /**
1024: * @see java.lang.Object#hashCode
1025: * @return a hashcode based on the version, main and sections.
1026: */
1027: public int hashCode() {
1028: int hashCode = 0;
1029:
1030: if (manifestVersion != null) {
1031: hashCode += manifestVersion.hashCode();
1032: }
1033: hashCode += mainSection.hashCode();
1034: hashCode += sections.hashCode();
1035:
1036: return hashCode;
1037: }
1038:
1039: /**
1040: * @see java.lang.Object#equals
1041: * @param rhs the object to check for equality.
1042: * @return true if the version, main and sections are the same.
1043: */
1044: public boolean equals(Object rhs) {
1045: if (rhs == null || rhs.getClass() != getClass()) {
1046: return false;
1047: }
1048:
1049: if (rhs == this ) {
1050: return true;
1051: }
1052:
1053: Manifest rhsManifest = (Manifest) rhs;
1054: if (manifestVersion == null) {
1055: if (rhsManifest.manifestVersion != null) {
1056: return false;
1057: }
1058: } else if (!manifestVersion.equals(rhsManifest.manifestVersion)) {
1059: return false;
1060: }
1061:
1062: if (!mainSection.equals(rhsManifest.mainSection)) {
1063: return false;
1064: }
1065:
1066: return sections.equals(rhsManifest.sections);
1067: }
1068:
1069: /**
1070: * Get the version of the manifest
1071: *
1072: * @return the manifest's version string
1073: */
1074: public String getManifestVersion() {
1075: return manifestVersion;
1076: }
1077:
1078: /**
1079: * Get the main section of the manifest
1080: *
1081: * @return the main section of the manifest
1082: */
1083: public Section getMainSection() {
1084: return mainSection;
1085: }
1086:
1087: /**
1088: * Get a particular section from the manifest
1089: *
1090: * @param name the name of the section desired.
1091: * @return the specified section or null if that section
1092: * does not exist in the manifest
1093: */
1094: public Section getSection(String name) {
1095: return (Section) sections.get(name);
1096: }
1097:
1098: /**
1099: * Get the section names in this manifest.
1100: *
1101: * @return an Enumeration of section names
1102: */
1103: public Enumeration getSectionNames() {
1104: return sectionIndex.elements();
1105: }
1106: }
|