KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > Manifest


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.BufferedReader JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.io.InputStreamReader JavaDoc;
25 import java.io.PrintWriter JavaDoc;
26 import java.io.Reader JavaDoc;
27 import java.io.StringWriter JavaDoc;
28 import java.io.UnsupportedEncodingException JavaDoc;
29 import java.util.Enumeration JavaDoc;
30 import java.util.Hashtable JavaDoc;
31 import java.util.Vector JavaDoc;
32 import org.apache.tools.ant.BuildException;
33 import org.apache.tools.ant.util.FileUtils;
34
35 /**
36  * Holds the data of a jar manifest.
37  *
38  * Manifests are processed according to the
39  * {@link <a HREF="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html">Jar
40  * file specification.</a>}.
41  * Specifically, a manifest element consists of
42  * a set of attributes and sections. These sections in turn may contain
43  * attributes. Note in particular that this may result in manifest lines
44  * greater than 72 bytes being wrapped and continued on the next
45  * line. If an application can not handle the continuation mechanism, it
46  * is a defect in the application, not this task.
47  *
48  *
49  * @since Ant 1.4
50  */

51 public class Manifest {
52     /** The standard manifest version header */
53     public static final String JavaDoc ATTRIBUTE_MANIFEST_VERSION
54         = "Manifest-Version";
55
56     /** The standard Signature Version header */
57     public static final String JavaDoc ATTRIBUTE_SIGNATURE_VERSION
58         = "Signature-Version";
59
60     /** The Name Attribute is the first in a named section */
61     public static final String JavaDoc ATTRIBUTE_NAME = "Name";
62
63     /** The From Header is disallowed in a Manifest */
64     public static final String JavaDoc ATTRIBUTE_FROM = "From";
65
66     /** The Class-Path Header is special - it can be duplicated */
67     public static final String JavaDoc ATTRIBUTE_CLASSPATH = "Class-Path";
68
69     /** Default Manifest version if one is not specified */
70     public static final String JavaDoc DEFAULT_MANIFEST_VERSION = "1.0";
71
72     /** The max length of a line in a Manifest */
73     public static final int MAX_LINE_LENGTH = 72;
74
75     /**
76      * Max length of a line section which is continued. Need to allow
77      * for the CRLF.
78      */

79     public static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2;
80
81     /** The End-Of-Line marker in manifests */
82     public static final String JavaDoc EOL = "\r\n";
83     /** Error for attributes */
84     public static final String JavaDoc ERROR_FROM_FORBIDDEN = "Manifest attributes should not start "
85                         + "with \"" + ATTRIBUTE_FROM + "\" in \"";
86
87     /** Encoding to be used for JAR files. */
88     public static final String JavaDoc JAR_ENCODING = "UTF-8";
89
90     /**
91      * An attribute for the manifest.
92      * Those attributes that are not nested into a section will be added to the "Main" section.
93      */

94     public static class Attribute {
95
96         /**
97          * Maximum length of the name to have the value starting on the same
98          * line as the name. This to stay under 72 bytes total line length
99          * (including CRLF).
100          */

101         private static final int MAX_NAME_VALUE_LENGTH = 68;
102
103         /**
104          * Maximum length of the name according to the jar specification.
105          * In this case the first line will have 74 bytes total line length
106          * (including CRLF). This conflicts with the 72 bytes total line length
107          * max, but is the only possible conclusion from the manifest specification, if
108          * names with 70 bytes length are allowed, have to be on the first line, and
109          * have to be followed by ": ".
110          */

111         private static final int MAX_NAME_LENGTH = 70;
112
113         /** The attribute's name */
114         private String JavaDoc name = null;
115
116         /** The attribute's value */
117         private Vector JavaDoc values = new Vector JavaDoc();
118
119         /**
120          * For multivalued attributes, this is the index of the attribute
121          * currently being defined.
122          */

123         private int currentIndex = 0;
124
125         /**
126          * Construct an empty attribute */

127         public Attribute() {
128         }
129
130         /**
131          * Construct an attribute by parsing a line from the Manifest
132          *
133          * @param line the line containing the attribute name and value
134          *
135          * @throws ManifestException if the line is not valid
136          */

137         public Attribute(String JavaDoc line) throws ManifestException {
138             parse(line);
139         }
140
141         /**
142          * Construct a manifest by specifying its name and value
143          *
144          * @param name the attribute's name
145          * @param value the Attribute's value
146          */

147         public Attribute(String JavaDoc name, String JavaDoc value) {
148             this.name = name;
149             setValue(value);
150         }
151
152         /**
153          * @see java.lang.Object#hashCode
154          * @return a hashcode based on the key and values.
155          */

156         public int hashCode() {
157             int hashCode = 0;
158
159             if (name != null) {
160                 hashCode += getKey().hashCode();
161             }
162
163             hashCode += values.hashCode();
164             return hashCode;
165         }
166
167         /**
168          * @param rhs the object to check for equality.
169          * @see java.lang.Object#equals
170          * @return true if the key and values are the same.
171          */

172         public boolean equals(Object JavaDoc rhs) {
173             if (rhs == null || rhs.getClass() != getClass()) {
174                 return false;
175             }
176
177             if (rhs == this) {
178                 return true;
179             }
180
181             Attribute rhsAttribute = (Attribute) rhs;
182             String JavaDoc lhsKey = getKey();
183             String JavaDoc rhsKey = rhsAttribute.getKey();
184             if ((lhsKey == null && rhsKey != null)
185                  || (lhsKey != null && rhsKey == null)
186                  || !lhsKey.equals(rhsKey)) {
187                 return false;
188             }
189
190             return values.equals(rhsAttribute.values);
191         }
192
193         /**
194          * Parse a line into name and value pairs
195          *
196          * @param line the line to be parsed
197          *
198          * @throws ManifestException if the line does not contain a colon
199          * separating the name and value
200          */

201         public void parse(String JavaDoc line) throws ManifestException {
202             int index = line.indexOf(": ");
203             if (index == -1) {
204                 throw new ManifestException("Manifest line \"" + line
205                     + "\" is not valid as it does not "
206                     + "contain a name and a value separated by ': ' ");
207             }
208             name = line.substring(0, index);
209             setValue(line.substring(index + 2));
210         }
211
212         /**
213          * Set the Attribute's name; required
214          *
215          * @param name the attribute's name
216          */

217         public void setName(String JavaDoc name) {
218             this.name = name;
219         }
220
221         /**
222          * Get the Attribute's name
223          *
224          * @return the attribute's name.
225          */

226         public String JavaDoc getName() {
227             return name;
228         }
229
230         /**
231          * Get the attribute's Key - its name in lower case.
232          *
233          * @return the attribute's key.
234          */

235         public String JavaDoc getKey() {
236             if (name == null) {
237                 return null;
238             }
239             return name.toLowerCase();
240         }
241
242         /**
243          * Set the Attribute's value; required
244          *
245          * @param value the attribute's value
246          */

247         public void setValue(String JavaDoc value) {
248             if (currentIndex >= values.size()) {
249                 values.addElement(value);
250                 currentIndex = values.size() - 1;
251             } else {
252                 values.setElementAt(value, currentIndex);
253             }
254         }
255
256         /**
257          * Get the Attribute's value.
258          *
259          * @return the attribute's value.
260          */

261         public String JavaDoc getValue() {
262             if (values.size() == 0) {
263                 return null;
264             }
265
266             String JavaDoc fullValue = "";
267             for (Enumeration JavaDoc e = getValues(); e.hasMoreElements();) {
268                 String JavaDoc value = (String JavaDoc) e.nextElement();
269                 fullValue += value + " ";
270             }
271             return fullValue.trim();
272         }
273
274         /**
275          * Add a new value to this attribute - making it multivalued.
276          *
277          * @param value the attribute's additional value
278          */

279         public void addValue(String JavaDoc value) {
280             currentIndex++;
281             setValue(value);
282         }
283
284         /**
285          * Get all the attribute's values.
286          *
287          * @return an enumeration of the attributes values
288          */

289         public Enumeration JavaDoc getValues() {
290             return values.elements();
291         }
292
293         /**
294          * Add a continuation line from the Manifest file.
295          *
296          * When lines are too long in a manifest, they are continued on the
297          * next line by starting with a space. This method adds the continuation
298          * data to the attribute value by skipping the first character.
299          *
300          * @param line the continuation line.
301          */

302         public void addContinuation(String JavaDoc line) {
303             String JavaDoc currentValue = (String JavaDoc) values.elementAt(currentIndex);
304             setValue(currentValue + line.substring(1));
305         }
306
307         /**
308          * Write the attribute out to a print writer.
309          *
310          * @param writer the Writer to which the attribute is written
311          *
312          * @throws IOException if the attribute value cannot be written
313          */

314         public void write(PrintWriter JavaDoc writer) throws IOException JavaDoc {
315             for (Enumeration JavaDoc e = getValues(); e.hasMoreElements();) {
316                 writeValue(writer, (String JavaDoc) e.nextElement());
317             }
318         }
319
320         /**
321          * Write a single attribute value out
322          *
323          * @param writer the Writer to which the attribute is written
324          * @param value the attribute value
325          *
326          * @throws IOException if the attribute value cannot be written
327          */

328         private void writeValue(PrintWriter JavaDoc writer, String JavaDoc value)
329              throws IOException JavaDoc {
330             String JavaDoc line = null;
331             int nameLength = name.getBytes(JAR_ENCODING).length;
332             if (nameLength > MAX_NAME_VALUE_LENGTH) {
333                 if (nameLength > MAX_NAME_LENGTH) {
334                     throw new IOException JavaDoc("Unable to write manifest line "
335                             + name + ": " + value);
336                 }
337                 writer.print(name + ": " + EOL);
338                 line = " " + value;
339             } else {
340                 line = name + ": " + value;
341             }
342             while (line.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH) {
343                 // try to find a MAX_LINE_LENGTH byte section
344
int breakIndex = MAX_SECTION_LENGTH;
345                 if (breakIndex >= line.length()) {
346                     breakIndex = line.length() - 1;
347                 }
348                 String JavaDoc section = line.substring(0, breakIndex);
349                 while (section.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH
350                      && breakIndex > 0) {
351                     breakIndex--;
352                     section = line.substring(0, breakIndex);
353                 }
354                 if (breakIndex == 0) {
355                     throw new IOException JavaDoc("Unable to write manifest line "
356                         + name + ": " + value);
357                 }
358                 writer.print(section + EOL);
359                 line = " " + line.substring(breakIndex);
360             }
361             writer.print(line + EOL);
362         }
363     }
364
365     /**
366      * A manifest section - you can nest attribute elements into sections.
367      * A section consists of a set of attribute values,
368      * separated from other sections by a blank line.
369      */

370     public static class Section {
371         /** Warnings for this section */
372         private Vector JavaDoc warnings = new Vector JavaDoc();
373
374         /**
375          * The section's name if any. The main section in a
376          * manifest is unnamed.
377          */

378         private String JavaDoc name = null;
379
380         /** The section's attributes.*/
381         private Hashtable JavaDoc attributes = new Hashtable JavaDoc();
382
383         /** Index used to retain the attribute ordering */
384         private Vector JavaDoc attributeIndex = new Vector JavaDoc();
385
386         /**
387          * The name of the section; optional -default is the main section.
388          * @param name the section's name
389          */

390         public void setName(String JavaDoc name) {
391             this.name = name;
392         }
393
394         /**
395          * Get the Section's name.
396          *
397          * @return the section's name.
398          */

399         public String JavaDoc getName() {
400             return name;
401         }
402
403         /**
404          * Read a section through a reader.
405          *
406          * @param reader the reader from which the section is read
407          *
408          * @return the name of the next section if it has been read as
409          * part of this section - This only happens if the
410          * Manifest is malformed.
411          *
412          * @throws ManifestException if the section is not valid according
413          * to the JAR spec
414          * @throws IOException if the section cannot be read from the reader.
415          */

416         public String JavaDoc read(BufferedReader JavaDoc reader)
417              throws ManifestException, IOException JavaDoc {
418             Attribute attribute = null;
419             while (true) {
420                 String JavaDoc line = reader.readLine();
421                 if (line == null || line.length() == 0) {
422                     return null;
423                 }
424                 if (line.charAt(0) == ' ') {
425                     // continuation line
426
if (attribute == null) {
427                         if (name != null) {
428                             // a continuation on the first line is a
429
// continuation of the name - concatenate this
430
// line and the name
431
name += line.substring(1);
432                         } else {
433                             throw new ManifestException("Can't start an "
434                                 + "attribute with a continuation line " + line);
435                         }
436                     } else {
437                         attribute.addContinuation(line);
438                     }
439                 } else {
440                     attribute = new Attribute(line);
441                     String JavaDoc nameReadAhead = addAttributeAndCheck(attribute);
442                     // refresh attribute in case of multivalued attributes.
443
attribute = getAttribute(attribute.getKey());
444                     if (nameReadAhead != null) {
445                         return nameReadAhead;
446                     }
447                 }
448             }
449         }
450
451         /**
452          * Merge in another section
453          *
454          * @param section the section to be merged with this one.
455          *
456          * @throws ManifestException if the sections cannot be merged.
457          */

458         public void merge(Section section) throws ManifestException {
459             if (name == null && section.getName() != null
460                 || name != null
461                 && !(name.equalsIgnoreCase(section.getName()))) {
462                 throw new ManifestException("Unable to merge sections "
463                     + "with different names");
464             }
465
466             Enumeration JavaDoc e = section.getAttributeKeys();
467             Attribute classpathAttribute = null;
468             while (e.hasMoreElements()) {
469                 String JavaDoc attributeName = (String JavaDoc) e.nextElement();
470                 Attribute attribute = section.getAttribute(attributeName);
471                 if (attributeName.equalsIgnoreCase(ATTRIBUTE_CLASSPATH)) {
472                     if (classpathAttribute == null) {
473                         classpathAttribute = new Attribute();
474                         classpathAttribute.setName(ATTRIBUTE_CLASSPATH);
475                     }
476                     Enumeration JavaDoc cpe = attribute.getValues();
477                     while (cpe.hasMoreElements()) {
478                         String JavaDoc value = (String JavaDoc) cpe.nextElement();
479                         classpathAttribute.addValue(value);
480                     }
481                 } else {
482                     // the merge file always wins
483
storeAttribute(attribute);
484                 }
485             }
486
487             if (classpathAttribute != null) {
488                 // the merge file *always* wins, even for Class-Path
489
storeAttribute(classpathAttribute);
490             }
491
492             // add in the warnings
493
Enumeration JavaDoc warnEnum = section.warnings.elements();
494             while (warnEnum.hasMoreElements()) {
495                 warnings.addElement(warnEnum.nextElement());
496             }
497         }
498
499         /**
500          * Write the section out to a print writer.
501          *
502          * @param writer the Writer to which the section is written
503          *
504          * @throws IOException if the section cannot be written
505          */

506         public void write(PrintWriter JavaDoc writer) throws IOException JavaDoc {
507             if (name != null) {
508                 Attribute nameAttr = new Attribute(ATTRIBUTE_NAME, name);
509                 nameAttr.write(writer);
510             }
511             Enumeration JavaDoc e = getAttributeKeys();
512             while (e.hasMoreElements()) {
513                 String JavaDoc key = (String JavaDoc) e.nextElement();
514                 Attribute attribute = getAttribute(key);
515                 attribute.write(writer);
516             }
517             writer.print(EOL);
518         }
519
520         /**
521          * Get a attribute of the section
522          *
523          * @param attributeName the name of the attribute
524          * @return a Manifest.Attribute instance if the attribute is
525          * single-valued, otherwise a Vector of Manifest.Attribute
526          * instances.
527          */

528         public Attribute getAttribute(String JavaDoc attributeName) {
529             return (Attribute) attributes.get(attributeName.toLowerCase());
530         }
531
532         /**
533          * Get the attribute keys.
534          *
535          * @return an Enumeration of Strings, each string being the lower case
536          * key of an attribute of the section.
537          */

538         public Enumeration JavaDoc getAttributeKeys() {
539             return attributeIndex.elements();
540         }
541
542         /**
543          * Get the value of the attribute with the name given.
544          *
545          * @param attributeName the name of the attribute to be returned.
546          *
547          * @return the attribute's value or null if the attribute does not exist
548          * in the section
549          */

550         public String JavaDoc getAttributeValue(String JavaDoc attributeName) {
551             Attribute attribute = getAttribute(attributeName.toLowerCase());
552             if (attribute == null) {
553                 return null;
554             }
555             return attribute.getValue();
556         }
557
558         /**
559          * Remove the given attribute from the section
560          *
561          * @param attributeName the name of the attribute to be removed.
562          */

563         public void removeAttribute(String JavaDoc attributeName) {
564             String JavaDoc key = attributeName.toLowerCase();
565             attributes.remove(key);
566             attributeIndex.removeElement(key);
567         }
568
569         /**
570          * Add an attribute to the section.
571          *
572          * @param attribute the attribute to be added to the section
573          *
574          * @exception ManifestException if the attribute is not valid.
575          */

576         public void addConfiguredAttribute(Attribute attribute)
577              throws ManifestException {
578             String JavaDoc check = addAttributeAndCheck(attribute);
579             if (check != null) {
580                 throw new BuildException("Specify the section name using "
581                     + "the \"name\" attribute of the <section> element rather "
582                     + "than using a \"Name\" manifest attribute");
583             }
584         }
585
586         /**
587          * Add an attribute to the section
588          *
589          * @param attribute the attribute to be added.
590          *
591          * @return the value of the attribute if it is a name
592          * attribute - null other wise
593          *
594          * @exception ManifestException if the attribute already
595          * exists in this section.
596          */

597         public String JavaDoc addAttributeAndCheck(Attribute attribute)
598              throws ManifestException {
599             if (attribute.getName() == null || attribute.getValue() == null) {
600                 throw new BuildException("Attributes must have name and value");
601             }
602             if (attribute.getKey().equalsIgnoreCase(ATTRIBUTE_NAME)) {
603                 warnings.addElement("\"" + ATTRIBUTE_NAME + "\" attributes "
604                     + "should not occur in the main section and must be the "
605                     + "first element in all other sections: \""
606                     + attribute.getName() + ": " + attribute.getValue() + "\"");
607                 return attribute.getValue();
608             }
609
610             if (attribute.getKey().startsWith(ATTRIBUTE_FROM.toLowerCase())) {
611                 warnings.addElement(ERROR_FROM_FORBIDDEN
612                     + attribute.getName() + ": " + attribute.getValue() + "\"");
613             } else {
614                 // classpath attributes go into a vector
615
String JavaDoc attributeKey = attribute.getKey();
616                 if (attributeKey.equalsIgnoreCase(ATTRIBUTE_CLASSPATH)) {
617                     Attribute classpathAttribute =
618                         (Attribute) attributes.get(attributeKey);
619
620                     if (classpathAttribute == null) {
621                         storeAttribute(attribute);
622                     } else {
623                         warnings.addElement("Multiple Class-Path attributes "
624                             + "are supported but violate the Jar "
625                             + "specification and may not be correctly "
626                             + "processed in all environments");
627                         Enumeration JavaDoc e = attribute.getValues();
628                         while (e.hasMoreElements()) {
629                             String JavaDoc value = (String JavaDoc) e.nextElement();
630                             classpathAttribute.addValue(value);
631                         }
632                     }
633                 } else if (attributes.containsKey(attributeKey)) {
634                     throw new ManifestException("The attribute \""
635                         + attribute.getName() + "\" may not occur more "
636                         + "than once in the same section");
637                 } else {
638                     storeAttribute(attribute);
639                 }
640             }
641             return null;
642         }
643
644         /**
645          * Clone this section
646          *
647          * @return the cloned Section
648          * @since Ant 1.5.2
649          */

650         public Object JavaDoc clone() {
651             Section cloned = new Section();
652             cloned.setName(name);
653             Enumeration JavaDoc e = getAttributeKeys();
654             while (e.hasMoreElements()) {
655                 String JavaDoc key = (String JavaDoc) e.nextElement();
656                 Attribute attribute = getAttribute(key);
657                 cloned.storeAttribute(new Attribute(attribute.getName(),
658                                                     attribute.getValue()));
659             }
660             return cloned;
661         }
662
663         /**
664          * Store an attribute and update the index.
665          *
666          * @param attribute the attribute to be stored
667          */

668         private void storeAttribute(Attribute attribute) {
669             if (attribute == null) {
670                 return;
671             }
672             String JavaDoc attributeKey = attribute.getKey();
673             attributes.put(attributeKey, attribute);
674             if (!attributeIndex.contains(attributeKey)) {
675                 attributeIndex.addElement(attributeKey);
676             }
677         }
678
679         /**
680          * Get the warnings for this section.
681          *
682          * @return an Enumeration of warning strings.
683          */

684         public Enumeration JavaDoc getWarnings() {
685             return warnings.elements();
686         }
687
688         /**
689          * @see java.lang.Object#hashCode
690          * @return a hash value based on the attributes.
691          */

692         public int hashCode() {
693             return attributes.hashCode();
694         }
695
696         /**
697          * @see java.lang.Object#equals
698          * @param rhs the object to check for equality.
699          * @return true if the attributes are the same.
700          */

701         public boolean equals(Object JavaDoc rhs) {
702             if (rhs == null || rhs.getClass() != getClass()) {
703                 return false;
704             }
705
706             if (rhs == this) {
707                 return true;
708             }
709
710             Section rhsSection = (Section) rhs;
711
712             return attributes.equals(rhsSection.attributes);
713         }
714     }
715
716
717     /** The version of this manifest */
718     private String JavaDoc manifestVersion = DEFAULT_MANIFEST_VERSION;
719
720     /** The main section of this manifest */
721     private Section mainSection = new Section();
722
723     /** The named sections of this manifest */
724     private Hashtable JavaDoc sections = new Hashtable JavaDoc();
725
726     /** Index of sections - used to retain order of sections in manifest */
727     private Vector JavaDoc sectionIndex = new Vector JavaDoc();
728
729     /**
730      * Construct a manifest from Ant's default manifest file.
731      *
732      * @return the default manifest.
733      * @exception BuildException if there is a problem loading the
734      * default manifest
735      */

736     public static Manifest getDefaultManifest() throws BuildException {
737         InputStream JavaDoc in = null;
738         InputStreamReader JavaDoc insr = null;
739         try {
740             String JavaDoc defManifest = "/org/apache/tools/ant/defaultManifest.mf";
741             in = Manifest.class.getResourceAsStream(defManifest);
742             if (in == null) {
743                 throw new BuildException("Could not find default manifest: "
744                     + defManifest);
745             }
746             try {
747                 insr = new InputStreamReader JavaDoc(in, "UTF-8");
748                 Manifest defaultManifest = new Manifest(insr);
749