| 1 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.Enumeration ; 30 import java.util.Hashtable ; 31 import java.util.Vector ; 32 import org.apache.tools.ant.BuildException; 33 import org.apache.tools.ant.util.FileUtils; 34 35 51 public class Manifest { 52 53 public static final String ATTRIBUTE_MANIFEST_VERSION 54 = "Manifest-Version"; 55 56 57 public static final String ATTRIBUTE_SIGNATURE_VERSION 58 = "Signature-Version"; 59 60 61 public static final String ATTRIBUTE_NAME = "Name"; 62 63 64 public static final String ATTRIBUTE_FROM = "From"; 65 66 67 public static final String ATTRIBUTE_CLASSPATH = "Class-Path"; 68 69 70 public static final String DEFAULT_MANIFEST_VERSION = "1.0"; 71 72 73 public static final int MAX_LINE_LENGTH = 72; 74 75 79 public static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2; 80 81 82 public static final String EOL = "\r\n"; 83 84 public static final String ERROR_FROM_FORBIDDEN = "Manifest attributes should not start " 85 + "with \"" + ATTRIBUTE_FROM + "\" in \""; 86 87 88 public static final String JAR_ENCODING = "UTF-8"; 89 90 94 public static class Attribute { 95 96 101 private static final int MAX_NAME_VALUE_LENGTH = 68; 102 103 111 private static final int MAX_NAME_LENGTH = 70; 112 113 114 private String name = null; 115 116 117 private Vector values = new Vector (); 118 119 123 private int currentIndex = 0; 124 125 127 public Attribute() { 128 } 129 130 137 public Attribute(String line) throws ManifestException { 138 parse(line); 139 } 140 141 147 public Attribute(String name, String value) { 148 this.name = name; 149 setValue(value); 150 } 151 152 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 172 public boolean equals(Object 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 lhsKey = getKey(); 183 String 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 201 public void parse(String 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 217 public void setName(String name) { 218 this.name = name; 219 } 220 221 226 public String getName() { 227 return name; 228 } 229 230 235 public String getKey() { 236 if (name == null) { 237 return null; 238 } 239 return name.toLowerCase(); 240 } 241 242 247 public void setValue(String 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 261 public String getValue() { 262 if (values.size() == 0) { 263 return null; 264 } 265 266 String fullValue = ""; 267 for (Enumeration e = getValues(); e.hasMoreElements();) { 268 String value = (String ) e.nextElement(); 269 fullValue += value + " "; 270 } 271 return fullValue.trim(); 272 } 273 274 279 public void addValue(String value) { 280 currentIndex++; 281 setValue(value); 282 } 283 284 289 public Enumeration getValues() { 290 return values.elements(); 291 } 292 293 302 public void addContinuation(String line) { 303 String currentValue = (String ) values.elementAt(currentIndex); 304 setValue(currentValue + line.substring(1)); 305 } 306 307 314 public void write(PrintWriter writer) throws IOException { 315 for (Enumeration e = getValues(); e.hasMoreElements();) { 316 writeValue(writer, (String ) e.nextElement()); 317 } 318 } 319 320 328 private void writeValue(PrintWriter writer, String value) 329 throws IOException { 330 String 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 ("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 int breakIndex = MAX_SECTION_LENGTH; 345 if (breakIndex >= line.length()) { 346 breakIndex = line.length() - 1; 347 } 348 String 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 ("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 370 public static class Section { 371 372 private Vector warnings = new Vector (); 373 374 378 private String name = null; 379 380 381 private Hashtable attributes = new Hashtable (); 382 383 384 private Vector attributeIndex = new Vector (); 385 386 390 public void setName(String name) { 391 this.name = name; 392 } 393 394 399 public String getName() { 400 return name; 401 } 402 403 416 public String read(BufferedReader reader) 417 throws ManifestException, IOException { 418 Attribute attribute = null; 419 while (true) { 420 String line = reader.readLine(); 421 if (line == null || line.length() == 0) { 422 return null; 423 } 424 if (line.charAt(0) == ' ') { 425 if (attribute == null) { 427 if (name != null) { 428 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 nameReadAhead = addAttributeAndCheck(attribute); 442 attribute = getAttribute(attribute.getKey()); 444 if (nameReadAhead != null) { 445 return nameReadAhead; 446 } 447 } 448 } 449 } 450 451 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 e = section.getAttributeKeys(); 467 Attribute classpathAttribute = null; 468 while (e.hasMoreElements()) { 469 String attributeName = (String ) 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 cpe = attribute.getValues(); 477 while (cpe.hasMoreElements()) { 478 String value = (String ) cpe.nextElement(); 479 classpathAttribute.addValue(value); 480 } 481 } else { 482 storeAttribute(attribute); 484 } 485 } 486 487 if (classpathAttribute != null) { 488 storeAttribute(classpathAttribute); 490 } 491 492 Enumeration warnEnum = section.warnings.elements(); 494 while (warnEnum.hasMoreElements()) { 495 warnings.addElement(warnEnum.nextElement()); 496 } 497 } 498 499 506 public void write(PrintWriter writer) throws IOException { 507 if (name != null) { 508 Attribute nameAttr = new Attribute(ATTRIBUTE_NAME, name); 509 nameAttr.write(writer); 510 } 511 Enumeration e = getAttributeKeys(); 512 while (e.hasMoreElements()) { 513 String key = (String ) e.nextElement(); 514 Attribute attribute = getAttribute(key); 515 attribute.write(writer); 516 } 517 writer.print(EOL); 518 } 519 520 528 public Attribute getAttribute(String attributeName) { 529 return (Attribute) attributes.get(attributeName.toLowerCase()); 530 } 531 532 538 public Enumeration getAttributeKeys() { 539 return attributeIndex.elements(); 540 } 541 542 550 public String getAttributeValue(String attributeName) { 551 Attribute attribute = getAttribute(attributeName.toLowerCase()); 552 if (attribute == null) { 553 return null; 554 } 555 return attribute.getValue(); 556 } 557 558 563 public void removeAttribute(String attributeName) { 564 String key = attributeName.toLowerCase(); 565 attributes.remove(key); 566 attributeIndex.removeElement(key); 567 } 568 569 576 public void addConfiguredAttribute(Attribute attribute) 577 throws ManifestException { 578 String 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 597 public String 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 String 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 e = attribute.getValues(); 628 while (e.hasMoreElements()) { 629 String value = (String ) 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 650 public Object clone() { 651 Section cloned = new Section(); 652 cloned.setName(name); 653 Enumeration e = getAttributeKeys(); 654 while (e.hasMoreElements()) { 655 String key = (String ) e.nextElement(); 656 Attribute attribute = getAttribute(key); 657 cloned.storeAttribute(new Attribute(attribute.getName(), 658 attribute.getValue())); 659 } 660 return cloned; 661 } 662 663 668 private void storeAttribute(Attribute attribute) { 669 if (attribute == null) { 670 return; 671 } 672 String attributeKey = attribute.getKey(); 673 attributes.put(attributeKey, attribute); 674 if (!attributeIndex.contains(attributeKey)) { 675 attributeIndex.addElement(attributeKey); 676 } 677 } 678 679 684 public Enumeration getWarnings() { 685 return warnings.elements(); 686 } 687 688 692 public int hashCode() { 693 return attributes.hashCode(); 694 } 695 696 701 public boolean equals(Object 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 718 private String manifestVersion = DEFAULT_MANIFEST_VERSION; 719 720 721 private Section mainSection = new Section(); 722 723 724 private Hashtable sections = new Hashtable (); 725 726 727 private Vector sectionIndex = new Vector (); 728 729 736 public static Manifest getDefaultManifest() throws BuildException { 737 InputStream in = null; 738 InputStreamReader insr = null; 739 try { 740 String 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 (in, "UTF-8"); 748 Manifest defaultManifest = new Manifest(insr); 749 |