Use canonical download URL for SonarQube tasks
[ant.git] / src / main / org / apache / tools / ant / taskdefs / Manifest.java
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;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.PrintWriter;
26 import java.io.Reader;
27 import java.io.StringWriter;
28 import java.io.UnsupportedEncodingException;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.Vector;
37 import java.util.stream.Collectors;
38
39 import org.apache.tools.ant.BuildException;
40 import org.apache.tools.ant.util.CollectionUtils;
41 import org.apache.tools.ant.util.FileUtils;
42
43 /**
44 * Holds the data of a jar manifest.
45 *
46 * Manifests are processed according to the
47 * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html">Jar
48 * file specification</a>.
49 * Specifically, a manifest element consists of
50 * a set of attributes and sections. These sections in turn may contain
51 * attributes. Note in particular that this may result in manifest lines
52 * greater than 72 bytes being wrapped and continued on the next
53 * line. If an application can not handle the continuation mechanism, it
54 * is a defect in the application, not this task.
55 *
56 *
57 * @since Ant 1.4
58 */
59 public class Manifest {
60 /** The standard manifest version header */
61 public static final String ATTRIBUTE_MANIFEST_VERSION
62 = "Manifest-Version";
63
64 /** The standard Signature Version header */
65 public static final String ATTRIBUTE_SIGNATURE_VERSION
66 = "Signature-Version";
67
68 /** The Name Attribute is the first in a named section */
69 public static final String ATTRIBUTE_NAME = "Name";
70
71 /** The From Header is disallowed in a Manifest */
72 public static final String ATTRIBUTE_FROM = "From";
73
74 /** The Class-Path Header is special - it can be duplicated */
75 public static final String ATTRIBUTE_CLASSPATH = "Class-Path";
76
77 /** Default Manifest version if one is not specified */
78 public static final String DEFAULT_MANIFEST_VERSION = "1.0";
79
80 /** The max length of a line in a Manifest */
81 public static final int MAX_LINE_LENGTH = 72;
82
83 /**
84 * Max length of a line section which is continued. Need to allow
85 * for the CRLF.
86 */
87 public static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2;
88
89 /** The End-Of-Line marker in manifests */
90 public static final String EOL = "\r\n";
91 /** Error for attributes */
92 public static final String ERROR_FROM_FORBIDDEN = "Manifest attributes should not start "
93 + "with \"" + ATTRIBUTE_FROM + "\" in \"";
94
95 /** Encoding to be used for JAR files. */
96 public static final String JAR_ENCODING = "UTF-8";
97
98 private static final String ATTRIBUTE_MANIFEST_VERSION_LC =
99 ATTRIBUTE_MANIFEST_VERSION.toLowerCase(Locale.ENGLISH);
100 private static final String ATTRIBUTE_NAME_LC =
101 ATTRIBUTE_NAME.toLowerCase(Locale.ENGLISH);
102 private static final String ATTRIBUTE_FROM_LC =
103 ATTRIBUTE_FROM.toLowerCase(Locale.ENGLISH);
104 private static final String ATTRIBUTE_CLASSPATH_LC =
105 ATTRIBUTE_CLASSPATH.toLowerCase(Locale.ENGLISH);
106
107 /**
108 * An attribute for the manifest.
109 * Those attributes that are not nested into a section will be added to the "Main" section.
110 */
111 public static class Attribute {
112
113 /**
114 * Maximum length of the name to have the value starting on the same
115 * line as the name. This to stay under 72 bytes total line length
116 * (including CRLF).
117 */
118 private static final int MAX_NAME_VALUE_LENGTH = 68;
119
120 /**
121 * Maximum length of the name according to the jar specification.
122 * In this case the first line will have 74 bytes total line length
123 * (including CRLF). This conflicts with the 72 bytes total line length
124 * max, but is the only possible conclusion from the manifest specification, if
125 * names with 70 bytes length are allowed, have to be on the first line, and
126 * have to be followed by ": ".
127 */
128 private static final int MAX_NAME_LENGTH = 70;
129
130 /** The attribute's name */
131 private String name = null;
132
133 /** The attribute's value */
134 private Vector<String> values = new Vector<String>();
135
136 /**
137 * For multivalued attributes, this is the index of the attribute
138 * currently being defined.
139 */
140 private int currentIndex = 0;
141
142 /**
143 * Construct an empty attribute */
144 public Attribute() {
145 }
146
147 /**
148 * Construct an attribute by parsing a line from the Manifest
149 *
150 * @param line the line containing the attribute name and value
151 *
152 * @throws ManifestException if the line is not valid
153 */
154 public Attribute(String line) throws ManifestException {
155 parse(line);
156 }
157
158 /**
159 * Construct a manifest by specifying its name and value
160 *
161 * @param name the attribute's name
162 * @param value the Attribute's value
163 */
164 public Attribute(String name, String value) {
165 this.name = name;
166 setValue(value);
167 }
168
169 /**
170 * @see java.lang.Object#hashCode
171 * @return a hashcode based on the key and values.
172 */
173 @Override
174 public int hashCode() {
175 return Objects.hash(getKey(), values);
176 }
177
178 /**
179 * @param rhs the object to check for equality.
180 * @see java.lang.Object#equals
181 * @return true if the key and values are the same.
182 */
183 @Override
184 public boolean equals(Object rhs) {
185 if (rhs == null || rhs.getClass() != getClass()) {
186 return false;
187 }
188
189 if (rhs == this) {
190 return true;
191 }
192
193 Attribute rhsAttribute = (Attribute) rhs;
194 String lhsKey = getKey();
195 String rhsKey = rhsAttribute.getKey();
196 if ((lhsKey == null && rhsKey != null)
197 || (lhsKey != null && !lhsKey.equals(rhsKey))) {
198 return false;
199 }
200
201 return values.equals(rhsAttribute.values);
202 }
203
204 /**
205 * Parse a line into name and value pairs
206 *
207 * @param line the line to be parsed
208 *
209 * @throws ManifestException if the line does not contain a colon
210 * separating the name and value
211 */
212 public void parse(String line) throws ManifestException {
213 int index = line.indexOf(": ");
214 if (index == -1) {
215 throw new ManifestException("Manifest line \"" + line
216 + "\" is not valid as it does not contain a name and a value separated by ': '");
217 }
218 name = line.substring(0, index);
219 setValue(line.substring(index + 2));
220 }
221
222 /**
223 * Set the Attribute's name; required
224 *
225 * @param name the attribute's name
226 */
227 public void setName(String name) {
228 this.name = name;
229 }
230
231 /**
232 * Get the Attribute's name
233 *
234 * @return the attribute's name.
235 */
236 public String getName() {
237 return name;
238 }
239
240 /**
241 * Get the attribute's Key - its name in lower case.
242 *
243 * @return the attribute's key.
244 */
245 public String getKey() {
246 if (name == null) {
247 return null;
248 }
249 return name.toLowerCase(Locale.ENGLISH);
250 }
251
252 /**
253 * Set the Attribute's value; required
254 *
255 * @param value the attribute's value
256 */
257 public void setValue(String value) {
258 if (currentIndex >= values.size()) {
259 values.addElement(value);
260 currentIndex = values.size() - 1;
261 } else {
262 values.setElementAt(value, currentIndex);
263 }
264 }
265
266 /**
267 * Get the Attribute's value.
268 *
269 * @return the attribute's value.
270 */
271 public String getValue() {
272 return values.isEmpty() ? null
273 : values.stream().collect(Collectors.joining(" "));
274 }
275
276 /**
277 * Add a new value to this attribute - making it multivalued.
278 *
279 * @param value the attribute's additional value
280 */
281 public void addValue(String value) {
282 currentIndex++;
283 setValue(value);
284 }
285
286 /**
287 * Get all the attribute's values.
288 *
289 * @return an enumeration of the attributes values
290 */
291 public Enumeration<String> getValues() {
292 return values.elements();
293 }
294
295 /**
296 * Add a continuation line from the Manifest file.
297 *
298 * When lines are too long in a manifest, they are continued on the
299 * next line by starting with a space. This method adds the continuation
300 * data to the attribute value by skipping the first character.
301 *
302 * @param line the continuation line.
303 */
304 public void addContinuation(String line) {
305 String currentValue = values.elementAt(currentIndex);
306 setValue(currentValue + line.substring(1));
307 }
308
309 /**
310 * Write the attribute out to a print writer without
311 * flattening multi-values attributes (i.e. Class-Path).
312 *
313 * @param writer the Writer to which the attribute is written
314 *
315 * @throws IOException if the attribute value cannot be written
316 */
317 public void write(PrintWriter writer) throws IOException {
318 write(writer, false);
319 }
320
321 /**
322 * Write the attribute out to a print writer.
323 *
324 * @param writer the Writer to which the attribute is written
325 * @param flatten whether to collapse multi-valued attributes
326 * (i.e. potentially Class-Path) Class-Path into a
327 * single attribute.
328 *
329 * @throws IOException if the attribute value cannot be written
330 * @since Ant 1.8.0
331 */
332 public void write(PrintWriter writer, boolean flatten)
333 throws IOException {
334 if (flatten) {
335 writeValue(writer, getValue());
336 } else {
337 for (String value : values) {
338 writeValue(writer, value);
339 }
340 }
341 }
342
343 /**
344 * Write a single attribute value out
345 *
346 * @param writer the Writer to which the attribute is written
347 * @param value the attribute value
348 *
349 * @throws IOException if the attribute value cannot be written
350 */
351 private void writeValue(PrintWriter writer, String value)
352 throws IOException {
353 String line;
354 int nameLength = name.getBytes(JAR_ENCODING).length;
355 if (nameLength > MAX_NAME_VALUE_LENGTH) {
356 if (nameLength > MAX_NAME_LENGTH) {
357 throw new IOException("Unable to write manifest line "
358 + name + ": " + value);
359 }
360 writer.print(name + ": " + EOL);
361 line = " " + value;
362 } else {
363 line = name + ": " + value;
364 }
365 while (line.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH) {
366 // try to find a MAX_LINE_LENGTH byte section
367 int breakIndex = MAX_SECTION_LENGTH;
368 if (breakIndex >= line.length()) {
369 breakIndex = line.length() - 1;
370 }
371 String section = line.substring(0, breakIndex);
372 while (section.getBytes(JAR_ENCODING).length > MAX_SECTION_LENGTH
373 && breakIndex > 0) {
374 breakIndex--;
375 section = line.substring(0, breakIndex);
376 }
377 if (breakIndex == 0) {
378 throw new IOException("Unable to write manifest line "
379 + name + ": " + value);
380 }
381 writer.print(section + EOL);
382 line = " " + line.substring(breakIndex);
383 }
384 writer.print(line + EOL);
385 }
386 }
387
388 /**
389 * A manifest section - you can nest attribute elements into sections.
390 * A section consists of a set of attribute values,
391 * separated from other sections by a blank line.
392 */
393 public static class Section {
394 /** Warnings for this section */
395 private List<String> warnings = new Vector<>();
396
397 /**
398 * The section's name if any. The main section in a
399 * manifest is unnamed.
400 */
401 private String name = null;
402
403 /** The section's attributes.*/
404 private Map<String, Attribute> attributes = new LinkedHashMap<>();
405
406 /**
407 * The name of the section; optional -default is the main section.
408 * @param name the section's name
409 */
410 public void setName(String name) {
411 this.name = name;
412 }
413
414 /**
415 * Get the Section's name.
416 *
417 * @return the section's name.
418 */
419 public String getName() {
420 return name;
421 }
422
423 /**
424 * Read a section through a reader.
425 *
426 * @param reader the reader from which the section is read
427 *
428 * @return the name of the next section if it has been read as
429 * part of this section - This only happens if the
430 * Manifest is malformed.
431 *
432 * @throws ManifestException if the section is not valid according
433 * to the JAR spec
434 * @throws IOException if the section cannot be read from the reader.
435 */
436 public String read(BufferedReader reader)
437 throws ManifestException, IOException {
438 Attribute attribute = null;
439 while (true) {
440 String line = reader.readLine();
441 if (line == null || line.isEmpty()) {
442 return null;
443 }
444 if (line.charAt(0) == ' ') {
445 // continuation line
446 if (attribute == null) {
447 if (name == null) {
448 throw new ManifestException("Can't start an "
449 + "attribute with a continuation line " + line);
450 }
451 // a continuation on the first line is a
452 // continuation of the name - concatenate this
453 // line and the name
454 name += line.substring(1);
455 } else {
456 attribute.addContinuation(line);
457 }
458 } else {
459 attribute = new Attribute(line);
460 String nameReadAhead = addAttributeAndCheck(attribute);
461 // refresh attribute in case of multivalued attributes.
462 attribute = getAttribute(attribute.getKey());
463 if (nameReadAhead != null) {
464 return nameReadAhead;
465 }
466 }
467 }
468 }
469
470 /**
471 * Merge in another section without merging Class-Path attributes.
472 *
473 * @param section the section to be merged with this one.
474 *
475 * @throws ManifestException if the sections cannot be merged.
476 */
477 public void merge(Section section) throws ManifestException {
478 merge(section, false);
479 }
480
481 /**
482 * Merge in another section
483 *
484 * @param section the section to be merged with this one.
485 * @param mergeClassPaths whether Class-Path attributes should
486 * be merged.
487 *
488 * @throws ManifestException if the sections cannot be merged.
489 */
490 public void merge(Section section, boolean mergeClassPaths)
491 throws ManifestException {
492 if (name == null && section.getName() != null
493 || (name != null && section.getName() != null
494 && !(name.toLowerCase(Locale.ENGLISH)
495 .equals(section.getName().toLowerCase(Locale.ENGLISH))))
496 ) {
497 throw new ManifestException(
498 "Unable to merge sections with different names");
499 }
500
501 Enumeration<String> e = section.getAttributeKeys();
502 Attribute classpathAttribute = null;
503 while (e.hasMoreElements()) {
504 String attributeName = e.nextElement();
505 Attribute attribute = section.getAttribute(attributeName);
506 if (ATTRIBUTE_CLASSPATH.equalsIgnoreCase(attributeName)) {
507 if (classpathAttribute == null) {
508 classpathAttribute = new Attribute();
509 classpathAttribute.setName(ATTRIBUTE_CLASSPATH);
510 }
511 Enumeration<String> cpe = attribute.getValues();
512 while (cpe.hasMoreElements()) {
513 String value = cpe.nextElement();
514 classpathAttribute.addValue(value);
515 }
516 } else {
517 // the merge file always wins
518 storeAttribute(attribute);
519 }
520 }
521
522 if (classpathAttribute != null) {
523 if (mergeClassPaths) {
524 Attribute currentCp = getAttribute(ATTRIBUTE_CLASSPATH);
525 if (currentCp != null) {
526 for (Enumeration<String> attribEnum = currentCp.getValues();
527 attribEnum.hasMoreElements();) {
528 String value = attribEnum.nextElement();
529 classpathAttribute.addValue(value);
530 }
531 }
532 }
533 storeAttribute(classpathAttribute);
534 }
535
536 // add in the warnings
537 warnings.addAll(section.warnings);
538 }
539
540 /**
541 * Write the section out to a print writer without flattening
542 * multi-values attributes (i.e. Class-Path).
543 *
544 * @param writer the Writer to which the section is written
545 *
546 * @throws IOException if the section cannot be written
547 */
548 public void write(PrintWriter writer) throws IOException {
549 write(writer, false);
550 }
551
552 /**
553 * Write the section out to a print writer.
554 *
555 * @param writer the Writer to which the section is written
556 * @param flatten whether to collapse multi-valued attributes
557 * (i.e. potentially Class-Path) Class-Path into a
558 * single attribute.
559 *
560 * @throws IOException if the section cannot be written
561 * @since Ant 1.8.0
562 */
563 public void write(PrintWriter writer, boolean flatten)
564 throws IOException {
565 if (name != null) {
566 Attribute nameAttr = new Attribute(ATTRIBUTE_NAME, name);
567 nameAttr.write(writer);
568 }
569 Enumeration<String> e = getAttributeKeys();
570 while (e.hasMoreElements()) {
571 String key = e.nextElement();
572 Attribute attribute = getAttribute(key);
573 attribute.write(writer, flatten);
574 }
575 writer.print(EOL);
576 }
577
578 /**
579 * Get a attribute of the section
580 *
581 * @param attributeName the name of the attribute
582 * @return a Manifest.Attribute instance if the attribute is
583 * single-valued, otherwise a Vector of Manifest.Attribute
584 * instances.
585 */
586 public Attribute getAttribute(String attributeName) {
587 return attributes.get(attributeName.toLowerCase(Locale.ENGLISH));
588 }
589
590 /**
591 * Get the attribute keys.
592 *
593 * @return an Enumeration of Strings, each string being the lower case
594 * key of an attribute of the section.
595 */
596 public Enumeration<String> getAttributeKeys() {
597 return CollectionUtils.asEnumeration(attributes.keySet().iterator());
598 }
599
600 /**
601 * Get the value of the attribute with the name given.
602 *
603 * @param attributeName the name of the attribute to be returned.
604 *
605 * @return the attribute's value or null if the attribute does not exist
606 * in the section
607 */
608 public String getAttributeValue(String attributeName) {
609 Attribute attribute = getAttribute(attributeName.toLowerCase(Locale.ENGLISH));
610 if (attribute == null) {
611 return null;
612 }
613 return attribute.getValue();
614 }
615
616 /**
617 * Remove the given attribute from the section
618 *
619 * @param attributeName the name of the attribute to be removed.
620 */
621 public void removeAttribute(String attributeName) {
622 String key = attributeName.toLowerCase(Locale.ENGLISH);
623 attributes.remove(key);
624 }
625
626 /**
627 * Add an attribute to the section.
628 *
629 * @param attribute the attribute to be added to the section
630 *
631 * @exception ManifestException if the attribute is not valid.
632 */
633 public void addConfiguredAttribute(Attribute attribute)
634 throws ManifestException {
635 String check = addAttributeAndCheck(attribute);
636 if (check != null) {
637 throw new BuildException(
638 "Specify the section name using the \"name\" attribute of the <section> element rather than using a \"Name\" manifest attribute");
639 }
640 }
641
642 /**
643 * Add an attribute to the section
644 *
645 * @param attribute the attribute to be added.
646 *
647 * @return the value of the attribute if it is a name
648 * attribute - null other wise
649 *
650 * @exception ManifestException if the attribute already
651 * exists in this section.
652 */
653 public String addAttributeAndCheck(Attribute attribute)
654 throws ManifestException {
655 if (attribute.getName() == null || attribute.getValue() == null) {
656 throw new BuildException("Attributes must have name and value");
657 }
658 String attributeKey = attribute.getKey();
659 if (attributeKey.equals(ATTRIBUTE_NAME_LC)) {
660 warnings.add("\"" + ATTRIBUTE_NAME
661 + "\" attributes should not occur in the main section and must be the first element in all other sections: \""
662 + attribute.getName() + ": " + attribute.getValue() + "\"");
663 return attribute.getValue();
664 }
665
666 if (attributeKey.startsWith(ATTRIBUTE_FROM_LC)) {
667 warnings.add(ERROR_FROM_FORBIDDEN
668 + attribute.getName() + ": " + attribute.getValue() + "\"");
669 } else {
670 // classpath attributes go into a vector
671 if (attributeKey.equals(ATTRIBUTE_CLASSPATH_LC)) {
672 Attribute classpathAttribute =
673 attributes.get(attributeKey);
674
675 if (classpathAttribute == null) {
676 storeAttribute(attribute);
677 } else {
678 warnings.add(
679 "Multiple Class-Path attributes are supported but violate the Jar specification and may not be correctly processed in all environments");
680 Enumeration<String> e = attribute.getValues();
681 while (e.hasMoreElements()) {
682 String value = e.nextElement();
683 classpathAttribute.addValue(value);
684 }
685 }
686 } else if (attributes.containsKey(attributeKey)) {
687 throw new ManifestException("The attribute \""
688 + attribute.getName()
689 + "\" may not occur more than once in the same section");
690 } else {
691 storeAttribute(attribute);
692 }
693 }
694 return null;
695 }
696
697 /**
698 * Clone this section
699 *
700 * @return the cloned Section
701 * @since Ant 1.5.2
702 */
703 @Override
704 public Section clone() {
705 Section cloned = new Section();
706 cloned.setName(name);
707 Enumeration<String> e = getAttributeKeys();
708 while (e.hasMoreElements()) {
709 String key = e.nextElement();
710 Attribute attribute = getAttribute(key);
711 cloned.storeAttribute(new Attribute(attribute.getName(),
712 attribute.getValue()));
713 }
714 return cloned;
715 }
716
717 /**
718 * Store an attribute and update the index.
719 *
720 * @param attribute the attribute to be stored
721 */
722 private void storeAttribute(Attribute attribute) {
723 if (attribute == null) {
724 return;
725 }
726 String attributeKey = attribute.getKey();
727 attributes.put(attributeKey, attribute);
728 }
729
730 /**
731 * Get the warnings for this section.
732 *
733 * @return an Enumeration of warning strings.
734 */
735 public Enumeration<String> getWarnings() {
736 return Collections.enumeration(warnings);
737 }
738
739 /**
740 * @see java.lang.Object#hashCode
741 * @return a hash value based on the attributes.
742 */
743 @Override
744 public int hashCode() {
745 return attributes.hashCode();
746 }
747
748 /**
749 * @see java.lang.Object#equals
750 * @param rhs the object to check for equality.
751 * @return true if the attributes are the same.
752 */
753 @Override
754 public boolean equals(Object rhs) {
755 if (rhs == null || rhs.getClass() != getClass()) {
756 return false;
757 }
758
759 if (rhs == this) {
760 return true;
761 }
762
763 Section rhsSection = (Section) rhs;
764
765 return attributes.equals(rhsSection.attributes);
766 }
767 }
768
769
770 /** The version of this manifest */
771 private String manifestVersion = DEFAULT_MANIFEST_VERSION;
772
773 /** The main section of this manifest */
774 private Section mainSection = new Section();
775
776 /** The named sections of this manifest */
777 private Map<String, Section> sections = new LinkedHashMap<>();
778
779 /**
780 * Construct a manifest from Ant's default manifest file.
781 *
782 * @return the default manifest.
783 * @exception BuildException if there is a problem loading the
784 * default manifest
785 */
786 public static Manifest getDefaultManifest() throws BuildException {
787 InputStreamReader insr = null;
788 String defManifest = "/org/apache/tools/ant/defaultManifest.mf";
789 try (InputStream in = Manifest.class.getResourceAsStream(defManifest)) {
790 if (in == null) {
791 throw new BuildException("Could not find default manifest: %s",
792 defManifest);
793 }
794 try {
795 insr = new InputStreamReader(in, "UTF-8");
796 Manifest defaultManifest = new Manifest(insr);
797 String version = System.getProperty("java.runtime.version");
798 if (version == null) {
799 version = System.getProperty("java.vm.version");
800 }
801 Attribute createdBy = new Attribute("Created-By",
802 version + " ("
803 + System.getProperty("java.vm.vendor") + ")");
804 defaultManifest.getMainSection().storeAttribute(createdBy);
805 return defaultManifest;
806 } catch (UnsupportedEncodingException e) {
807 insr = new InputStreamReader(in);
808 return new Manifest(insr);
809 }
810 } catch (ManifestException e) {
811 throw new BuildException("Default manifest is invalid !!", e);
812 } catch (IOException e) {
813 throw new BuildException("Unable to read default manifest", e);
814 } finally {
815 FileUtils.close(insr);
816 }
817 }
818
819 /** Construct an empty manifest */
820 public Manifest() {
821 manifestVersion = null;
822 }
823
824 /**
825 * Read a manifest file from the given reader
826 *
827 * @param r is the reader from which the Manifest is read
828 *
829 * @throws ManifestException if the manifest is not valid according
830 * to the JAR spec
831 * @throws IOException if the manifest cannot be read from the reader.
832 */
833 public Manifest(Reader r) throws ManifestException, IOException {
834 BufferedReader reader = new BufferedReader(r);
835 // This should be the manifest version
836 String nextSectionName = mainSection.read(reader);
837 String readManifestVersion
838 = mainSection.getAttributeValue(ATTRIBUTE_MANIFEST_VERSION);
839 if (readManifestVersion != null) {
840 manifestVersion = readManifestVersion;
841 mainSection.removeAttribute(ATTRIBUTE_MANIFEST_VERSION);
842 }
843
844 String line;
845 while ((line = reader.readLine()) != null) {
846 if (line.isEmpty()) {
847 continue;
848 }
849
850 Section section = new Section();
851 if (nextSectionName == null) {
852 Attribute sectionName = new Attribute(line);
853 if (!ATTRIBUTE_NAME.equalsIgnoreCase(sectionName.getName())) {
854 throw new ManifestException(
855 "Manifest sections should start with a \""
856 + ATTRIBUTE_NAME + "\" attribute and not \""
857 + sectionName.getName() + "\"");
858 }
859 nextSectionName = sectionName.getValue();
860 } else {
861 // we have already started reading this section
862 // this line is the first attribute. set it and then
863 // let the normal read handle the rest
864 Attribute firstAttribute = new Attribute(line);
865 section.addAttributeAndCheck(firstAttribute);
866 }
867
868 section.setName(nextSectionName);
869 nextSectionName = section.read(reader);
870 addConfiguredSection(section);
871 }
872 }
873
874 /**
875 * Add a section to the manifest
876 *
877 * @param section the manifest section to be added
878 *
879 * @exception ManifestException if the secti0on is not valid.
880 */
881 public void addConfiguredSection(Section section)
882 throws ManifestException {
883 String sectionName = section.getName();
884 if (sectionName == null) {
885 throw new BuildException("Sections must have a name");
886 }
887 sections.put(sectionName, section);
888 }
889
890 /**
891 * Add an attribute to the manifest - it is added to the main section.
892 *
893 * @param attribute the attribute to be added.
894 *
895 * @exception ManifestException if the attribute is not valid.
896 */
897 public void addConfiguredAttribute(Attribute attribute)
898 throws ManifestException {
899 if (attribute.getKey() == null || attribute.getValue() == null) {
900 throw new BuildException("Attributes must have name and value");
901 }
902 if (ATTRIBUTE_MANIFEST_VERSION_LC.equals(attribute.getKey())) {
903 manifestVersion = attribute.getValue();
904 } else {
905 mainSection.addConfiguredAttribute(attribute);
906 }
907 }
908
909 /**
910 * Merge the contents of the given manifest into this manifest
911 * without merging Class-Path attributes.
912 *
913 * @param other the Manifest to be merged with this one.
914 *
915 * @throws ManifestException if there is a problem merging the
916 * manifest according to the Manifest spec.
917 */
918 public void merge(Manifest other) throws ManifestException {
919 merge(other, false);
920 }
921
922 /**
923 * Merge the contents of the given manifest into this manifest
924 * without merging Class-Path attributes.
925 *
926 * @param other the Manifest to be merged with this one.
927 * @param overwriteMain whether to overwrite the main section
928 * of the current manifest
929 *
930 * @throws ManifestException if there is a problem merging the
931 * manifest according to the Manifest spec.
932 */
933 public void merge(Manifest other, boolean overwriteMain)
934 throws ManifestException {
935 merge(other, overwriteMain, false);
936 }
937
938 /**
939 * Merge the contents of the given manifest into this manifest
940 *
941 * @param other the Manifest to be merged with this one.
942 * @param overwriteMain whether to overwrite the main section
943 * of the current manifest
944 * @param mergeClassPaths whether Class-Path attributes should be
945 * merged.
946 *
947 * @throws ManifestException if there is a problem merging the
948 * manifest according to the Manifest spec.
949 *
950 * @since Ant 1.8.0
951 */
952 public void merge(Manifest other, boolean overwriteMain,
953 boolean mergeClassPaths)
954 throws ManifestException {
955 if (other != null) {
956 if (overwriteMain) {
957 mainSection = other.mainSection.clone();
958 } else {
959 mainSection.merge(other.mainSection, mergeClassPaths);
960 }
961
962 if (other.manifestVersion != null) {
963 manifestVersion = other.manifestVersion;
964 }
965
966 Enumeration<String> e = other.getSectionNames();
967 while (e.hasMoreElements()) {
968 String sectionName = e.nextElement();
969 Section ourSection = sections.get(sectionName);
970 Section otherSection
971 = other.sections.get(sectionName);
972 if (ourSection == null) {
973 if (otherSection != null) {
974 addConfiguredSection(otherSection.clone());
975 }
976 } else {
977 ourSection.merge(otherSection, mergeClassPaths);
978 }
979 }
980 }
981 }
982
983 /**
984 * Write the manifest out to a print writer without flattening
985 * multi-values attributes (i.e. Class-Path).
986 *
987 * @param writer the Writer to which the manifest is written
988 *
989 * @throws IOException if the manifest cannot be written
990 */
991 public void write(PrintWriter writer) throws IOException {
992 write(writer, false);
993 }
994
995 /**
996 * Write the manifest out to a print writer.
997 *
998 * @param writer the Writer to which the manifest is written
999 * @param flatten whether to collapse multi-valued attributes
1000 * (i.e. potentially Class-Path) Class-Path into a single
1001 * attribute.
1002 *
1003 * @throws IOException if the manifest cannot be written
1004 * @since Ant 1.8.0
1005 */
1006 public void write(PrintWriter writer, boolean flatten) throws IOException {
1007 writer.print(ATTRIBUTE_MANIFEST_VERSION + ": " + manifestVersion + EOL);
1008 String signatureVersion
1009 = mainSection.getAttributeValue(ATTRIBUTE_SIGNATURE_VERSION);
1010 if (signatureVersion != null) {
1011 writer.print(ATTRIBUTE_SIGNATURE_VERSION + ": "
1012 + signatureVersion + EOL);
1013 mainSection.removeAttribute(ATTRIBUTE_SIGNATURE_VERSION);
1014 }
1015 mainSection.write(writer, flatten);
1016
1017 // add it back
1018 if (signatureVersion != null) {
1019 try {
1020 Attribute svAttr = new Attribute(ATTRIBUTE_SIGNATURE_VERSION,
1021 signatureVersion);
1022 mainSection.addConfiguredAttribute(svAttr);
1023 } catch (ManifestException e) {
1024 // shouldn't happen - ignore
1025 }
1026 }
1027
1028 for (String sectionName : sections.keySet()) {
1029 Section section = getSection(sectionName);
1030 section.write(writer, flatten);
1031 }
1032 }
1033
1034 /**
1035 * Convert the manifest to its string representation
1036 *
1037 * @return a multiline string with the Manifest as it
1038 * appears in a Manifest file.
1039 */
1040 @Override
1041 public String toString() {
1042 StringWriter sw = new StringWriter();
1043 try {
1044 write(new PrintWriter(sw));
1045 } catch (IOException e) {
1046 return "";
1047 }
1048 return sw.toString();
1049 }
1050
1051 /**
1052 * Get the warnings for this manifest.
1053 *
1054 * @return an enumeration of warning strings
1055 */
1056 public Enumeration<String> getWarnings() {
1057 Vector<String> warnings = new Vector<>();
1058
1059 Enumeration<String> warnEnum = mainSection.getWarnings();
1060 while (warnEnum.hasMoreElements()) {
1061 warnings.addElement(warnEnum.nextElement());
1062 }
1063
1064 // create a vector and add in the warnings for all the sections
1065 for (Section section : sections.values()) {
1066 Enumeration<String> e2 = section.getWarnings();
1067 while (e2.hasMoreElements()) {
1068 warnings.addElement(e2.nextElement());
1069 }
1070 }
1071
1072 return warnings.elements();
1073 }
1074
1075 /**
1076 * @see java.lang.Object#hashCode
1077 * @return a hashcode based on the version, main and sections.
1078 */
1079 @Override
1080 public int hashCode() {
1081 int hashCode = 0;
1082
1083 if (manifestVersion != null) {
1084 hashCode += manifestVersion.hashCode();
1085 }
1086 hashCode += mainSection.hashCode();
1087 hashCode += sections.hashCode();
1088
1089 return hashCode;
1090 }
1091
1092 /**
1093 * @see java.lang.Object#equals
1094 * @param rhs the object to check for equality.
1095 * @return true if the version, main and sections are the same.
1096 */
1097 @Override
1098 public boolean equals(Object rhs) {
1099 if (rhs == null || rhs.getClass() != getClass()) {
1100 return false;
1101 }
1102
1103 if (rhs == this) {
1104 return true;
1105 }
1106
1107 Manifest rhsManifest = (Manifest) rhs;
1108 if (manifestVersion == null) {
1109 if (rhsManifest.manifestVersion != null) {
1110 return false;
1111 }
1112 } else if (!manifestVersion.equals(rhsManifest.manifestVersion)) {
1113 return false;
1114 }
1115
1116 if (!mainSection.equals(rhsManifest.mainSection)) {
1117 return false;
1118 }
1119
1120 return sections.equals(rhsManifest.sections);
1121 }
1122
1123 /**
1124 * Get the version of the manifest
1125 *
1126 * @return the manifest's version string
1127 */
1128 public String getManifestVersion() {
1129 return manifestVersion;
1130 }
1131
1132 /**
1133 * Get the main section of the manifest
1134 *
1135 * @return the main section of the manifest
1136 */
1137 public Section getMainSection() {
1138 return mainSection;
1139 }
1140
1141 /**
1142 * Get a particular section from the manifest
1143 *
1144 * @param name the name of the section desired.
1145 * @return the specified section or null if that section
1146 * does not exist in the manifest
1147 */
1148 public Section getSection(String name) {
1149 return sections.get(name);
1150 }
1151
1152 /**
1153 * Get the section names in this manifest.
1154 *
1155 * @return an Enumeration of section names
1156 */
1157 public Enumeration<String> getSectionNames() {
1158 return CollectionUtils.asEnumeration(sections.keySet().iterator());
1159 }
1160 }