1 /* 2 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package org.openjdk.jigsaw; 27 28 import java.io.*; 29 import java.nio.charset.Charset; 30 import java.security.*; 31 import java.util.*; 32 import java.util.Map.Entry; 33 import java.util.jar.*; 34 import org.openjdk.jigsaw.ModuleFileParser.Event; 35 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*; 36 import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*; 37 import static org.openjdk.jigsaw.ModuleFileParser.Event.*; 38 39 40 /** 41 * <p> A known <a 42 * href="http://cr.openjdk.java.net/~mr/jigsaw/notes/module-file-format/"> 43 * module file</a> </p> 44 */ 45 46 public final class ModuleFile { 47 48 /** 49 * Return the subdir of a section in an extracted module file. 50 */ 51 public static String getSubdirOfSection(SectionType type) { 52 switch (type) { 53 case MODULE_INFO: 54 case SIGNATURE: 55 return "."; 56 case CLASSES: 57 case RESOURCES: 58 return "classes"; 59 case NATIVE_LIBS: 60 return "lib"; 61 case NATIVE_CMDS: 62 return "bin"; 63 case CONFIG: 64 return "etc"; 65 default: 66 throw new AssertionError(type); 67 } 68 } 69 70 /** 71 * Returns a ModuleFileParser instance. 72 * 73 * @param stream 74 * module file stream 75 * 76 * @return a module file parser 77 * 78 * @throws ModuleFileParserException 79 * If there is an error processing the underlying module file 80 */ 81 public static ModuleFileParser newParser(InputStream stream) { 82 return new ModuleFileParserImpl(stream); 83 } 84 85 /** 86 * Returns a ValidatingModuleFileParser instance. 87 * 88 * @param stream 89 * module file stream 90 * 91 * @return a validating module file parser 92 * 93 * @throws ModuleFileParserException 94 * If there is an error processing the underlying module file 95 */ 96 public static ValidatingModuleFileParser newValidatingParser(InputStream stream) { 97 return new ValidatingModuleFileParserImpl(stream); 98 } 99 100 /** 101 * <p> A module-file reader </p> 102 */ 103 public final static class Reader implements Closeable { 104 private final ValidatingModuleFileParser parser; 105 private final ModuleFileHeader fileHeader; 106 private final byte[] moduleInfoBytes; 107 private final SignatureType moduleSignatureType; 108 private final byte[] moduleSignatureBytes ; 109 110 private File destination; 111 private boolean deflate; 112 private File natlibs; 113 private File natcmds; 114 private File configs; 115 116 public Reader(InputStream stream) throws IOException { 117 parser = ModuleFile.newValidatingParser(stream); 118 fileHeader = parser.fileHeader(); 119 // Read the MODULE_INFO and the Signature section (if present), 120 // but does not write any files. 121 parser.next(); 122 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 123 copyStream(parser.getRawStream(), baos); 124 moduleInfoBytes = baos.toByteArray(); 125 assert moduleInfoBytes != null; 126 127 if (parser.next() != END_SECTION) 128 throw new ModuleFileParserException( 129 "Expected END_SECTION of module-info"); 130 131 if (parser.hasNext()) { 132 Event event = parser.next(); 133 if (event != END_FILE) { // more sections 134 SectionHeader header = parser.getSectionHeader(); 135 if (header.type == SIGNATURE) { 136 SignatureSection sh = SignatureSection.read( 137 new DataInputStream(parser.getRawStream())); 138 moduleSignatureType = SignatureType.valueOf(sh.getSignatureType()); 139 moduleSignatureBytes = sh.getSignature(); 140 if (parser.next() != END_SECTION) 141 throw new ModuleFileParserException( 142 "Expected END_SECTION of signature"); 143 if (parser.hasNext()) 144 parser.next(); // position parser at next event 145 return; 146 } 147 } 148 } 149 // no signature section, or possibly other sections at all. 150 moduleSignatureBytes = null; 151 moduleSignatureType = null; 152 } 153 154 public void extractTo(File dst) throws IOException { 155 extractTo(dst, false); 156 } 157 158 public void extractTo(File dst, boolean deflate) throws IOException { 159 extractTo(dst, deflate, null, null, null); 160 } 161 162 public void extractTo(File dst, boolean deflate, File natlibs, 163 File natcmds, File configs) 164 throws IOException 165 { 166 this.deflate = deflate; 167 this.destination = dst != null ? dst.getCanonicalFile() : null; 168 this.natlibs = natlibs != null ? natlibs : new File(destination, "lib"); 169 this.natcmds = natcmds != null ? natcmds : new File(destination, "bin"); 170 this.configs = configs != null ? configs : new File(destination, "etc"); 171 172 try { 173 Files.store(moduleInfoBytes, computeRealPath("info")); 174 175 Event event = parser.event(); 176 if (event == END_FILE) 177 return; 178 179 if (event != START_SECTION) 180 throw new ModuleFileParserException( 181 "Expected START_SECTION, got : " + event); 182 // Module-Info and Signature, if present, have been consumed 183 do { 184 SectionHeader header = parser.getSectionHeader(); 185 SectionType type = header.getType(); 186 if (type.hasFiles()) { 187 while(parser.skipToNextStartSubSection()) { 188 readSubSection(type); 189 } 190 } else if (type == CLASSES) { 191 Iterator<Map.Entry<String,InputStream>> classes = 192 parser.getClasses(); 193 while (classes.hasNext()) { 194 Map.Entry<String,InputStream> entry = classes.next(); 195 try (OutputStream out = openOutputStream(type, entry.getKey())) { 196 copyStream(entry.getValue(), out); 197 } 198 } 199 // END_SECTION 200 parser.next(); 201 } else { 202 throw new IllegalArgumentException("Unknown type: " + type); 203 } 204 } while (parser.skipToNextStartSection()); 205 206 if (parser.event() != END_FILE) 207 throw new IOException("Expected END_FILE"); 208 } finally { 209 close(); 210 } 211 } 212 213 public byte[] getModuleInfoBytes() { 214 return moduleInfoBytes.clone(); 215 } 216 217 public byte[] getHash() { 218 return fileHeader.getHash(); 219 } 220 221 public ModuleArchitecture getArchitecture() { 222 return fileHeader.getArchitecture(); 223 } 224 225 public List<byte[]> getCalculatedHashes() { 226 List<byte[]> hashes = new ArrayList<>(); 227 hashes.add(parser.getHeaderHash()); 228 for (Entry<SectionType,byte[]> entry : parser.getHashes().entrySet()) { 229 if (entry.getKey() != SIGNATURE) 230 hashes.add(entry.getValue()); 231 } 232 hashes.add(getHash()); 233 234 return hashes; 235 } 236 237 public boolean hasSignature() { 238 return moduleSignatureBytes != null; 239 } 240 241 public SignatureType getSignatureType() { 242 return moduleSignatureType; 243 } 244 245 public byte[] getSignature() { 246 return moduleSignatureBytes == null ? null : 247 moduleSignatureBytes.clone(); 248 } 249 250 /*package*/ byte[] getSignatureNoClone() { 251 return moduleSignatureBytes; 252 } 253 254 public void close() throws IOException { 255 try { 256 if (contentStream != null) { 257 contentStream.close(); 258 contentStream = null; 259 } 260 } finally { 261 if (filesWriter != null) { 262 filesWriter.close(); 263 filesWriter = null; 264 } 265 } 266 } 267 268 // subsections/files (resources, libs, cmds, configs) 269 public void readSubSection(SectionType type) throws IOException { 270 assert type == RESOURCES || type == NATIVE_LIBS || 271 type == NATIVE_CMDS || type == CONFIG; 272 273 SubSectionFileHeader subHeader = parser.getSubSectionFileHeader(); 274 String path = subHeader.getPath(); 275 try (OutputStream sink = openOutputStream(type, path)) { 276 copyStream(parser.getContentStream(), sink); 277 } 278 279 // post processing for executable and files outside the module dir 280 postExtract(type, currentPath); 281 } 282 283 private JarOutputStream contentStream = null; 284 285 private JarOutputStream contentStream() throws IOException { 286 if (contentStream != null) 287 return contentStream; 288 289 return contentStream = new JarOutputStream( 290 new BufferedOutputStream( 291 new FileOutputStream(computeRealPath("classes")))); 292 } 293 294 private File currentPath = null; 295 296 private OutputStream openOutputStream(SectionType type, String path) 297 throws IOException 298 { 299 currentPath = null; 300 if (type == CLASSES || type == RESOURCES) 301 return Files.newOutputStream(contentStream(), deflate, path); 302 currentPath = computeRealPath(type, path); 303 File parent = currentPath.getParentFile(); 304 if (!parent.exists()) 305 Files.mkdirs(parent, currentPath.getName()); 306 return new BufferedOutputStream(new FileOutputStream(currentPath)); 307 } 308 309 // Track files installed outside the module library. For later removal. 310 // files are relative to the modules directory. 311 private PrintWriter filesWriter; 312 313 private void trackFiles(File file) 314 throws IOException 315 { 316 if (file == null || file.toPath().startsWith(destination.toPath())) 317 return; 318 319 // Lazy construction, not all modules will need this. 320 if (filesWriter == null) 321 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8"); 322 323 filesWriter.println(Files.convertSeparator(relativize(destination, file))); 324 filesWriter.flush(); 325 } 326 327 List<IOException> remove() { 328 return ModuleFile.Reader.remove(destination); 329 } 330 331 // Removes a module, given its module install directory 332 static List<IOException> remove(File moduleDir) { 333 List<IOException> excs = new ArrayList<>(); 334 // Firstly remove any files installed outside of the module dir 335 File files = new File(moduleDir, "files"); 336 if (files.exists()) { 337 try { 338 List<String> filenames = 339 java.nio.file.Files.readAllLines(files.toPath(), 340 Charset.forName("UTF-8")); 341 for (String fn : filenames) { 342 try { 343 Files.delete(new File(moduleDir, 344 Files.platformSeparator(fn))); 345 } catch (IOException x) { 346 excs.add(x); 347 } 348 } 349 } catch (IOException x) { 350 excs.add(x); 351 } 352 } 353 354 excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath())); 355 return excs; 356 } 357 358 // Returns the absolute path of the given section type. 359 private File getDirOfSection(SectionType type) { 360 if (type == NATIVE_LIBS) 361 return natlibs; 362 else if (type == NATIVE_CMDS) 363 return natcmds; 364 else if (type == CONFIG) 365 return configs; 366 367 // resolve sub dir section paths against the modules directory 368 return new File(destination, ModuleFile.getSubdirOfSection(type)); 369 } 370 371 private File computeRealPath(String path) throws IOException { 372 return resolveAndNormalize(destination, path); 373 } 374 375 private File computeRealPath(SectionType type, String storedpath) 376 throws IOException 377 { 378 File sectionPath = getDirOfSection(type); 379 File realpath = new File(sectionPath, 380 Files.ensureNonAbsolute(Files.platformSeparator(storedpath))); 381 382 validatePath(sectionPath, realpath); 383 384 // Create the parent directories if necessary 385 File parent = realpath.getParentFile(); 386 if (!parent.exists()) 387 Files.mkdirs(parent, realpath.getName()); 388 389 return realpath; 390 } 391 392 private static void markNativeCodeExecutable(SectionType type, 393 File file) 394 { 395 if (type == NATIVE_CMDS || (type == NATIVE_LIBS && 396 System.getProperty("os.name").startsWith("Windows"))) 397 file.setExecutable(true); 398 } 399 400 private void postExtract(SectionType type, File path) 401 throws IOException 402 { 403 markNativeCodeExecutable(type, path); 404 trackFiles(path); 405 } 406 } 407 408 private static void checkCompressor(SectionType type, 409 Compressor compressor) { 410 411 if ((MODULE_INFO == type && Compressor.NONE != compressor) || 412 (CLASSES == type && Compressor.PACK200_GZIP != compressor)) 413 throw new IllegalArgumentException(type 414 + " may not use compressor " 415 + compressor); 416 } 417 418 private static void checkSubsectionCount(SectionType type, 419 short subsections) { 420 if (!type.hasFiles() && subsections != 0) 421 throw new IllegalArgumentException(type 422 + " subsection count not 0: " 423 + subsections); 424 else if (type.hasFiles() && subsections == 0) 425 throw new IllegalArgumentException(type + " subsection count is 0"); 426 } 427 428 private static void copyStream(InputStream in, OutputStream out) 429 throws IOException 430 { 431 byte[] buf = new byte[8192]; 432 int read; 433 while ((read = in.read(buf)) > 0) 434 out.write(buf, 0, read); 435 } 436 437 private static void ensureNonNegativity(long size, String parameter) { 438 if (size < 0) 439 throw new IllegalArgumentException(parameter + "<0: " + size); 440 } 441 442 private static void ensureNonNull(Object reference, String parameter) { 443 if (null == reference) 444 throw new IllegalArgumentException(parameter + " == null"); 445 } 446 447 private static void ensureMatch(int found, int expected, String field) 448 throws IOException 449 { 450 if (found != expected) 451 throw new IOException(field + " expected : " 452 + Integer.toHexString(expected) + " found: " 453 + Integer.toHexString(found)); 454 } 455 456 private static void ensureShortNativePath(File path, String name) 457 throws IOException 458 { 459 // TODO: check for native code file in a stricter way 460 if (path.canExecute() 461 && name.indexOf('/') != -1) 462 throw new IOException("Native code path too long: " + path); 463 } 464 465 private static void ensureValidFileSize(long size, File path) 466 throws IOException 467 { 468 if (size < 0 || size > Integer.MAX_VALUE) 469 throw new IOException("File " + path + " too large: " + size); 470 } 471 472 static MessageDigest getHashInstance(HashType hashtype) 473 throws IOException 474 { 475 try { 476 switch(hashtype) { 477 case SHA256: 478 return MessageDigest.getInstance("SHA-256"); 479 default: 480 throw new IOException("Unknown hash type: " + hashtype); 481 } 482 } 483 catch (NoSuchAlgorithmException ex) { 484 throw new IOException(hashtype + " not found", ex); 485 } 486 } 487 488 private static short getMUTF8Length(String name) { 489 short size = 2; 490 491 for (int i = name.length()-1; i >= 0; i--) { 492 char ch = name.charAt(i); 493 494 if ('\u0001' <= ch && ch <= '\u007F') 495 size += 1; 496 else if ('\u0000' == ch 497 || '\u0080' <= ch && ch <= '\u07FF') 498 size += 2; 499 else 500 size += 3; 501 } 502 503 return size; 504 } 505 506 private static String hashHexString(byte[] hash) { 507 StringBuilder hex = new StringBuilder("0x"); 508 for (int i = 0; i < hash.length; i++) { 509 int val = (hash[i] & 0xFF); 510 if (val <= 16) 511 hex.append("0"); 512 hex.append(Integer.toHexString(val)); 513 } 514 return hex.toString(); 515 } 516 517 private static File resolveAndNormalize(File directory, String path) 518 throws IOException 519 { 520 File realpath = new File(directory, path); 521 if (directory != null && 522 ! realpath.toPath().startsWith(directory.toPath())) 523 throw new IOException("Bogus relative path: " + path); 524 525 return realpath; 526 } 527 528 529 private static String relativize(File directory, File path) throws IOException { 530 return (directory.toPath().relativize(path.toPath().toRealPath())).toString(); 531 } 532 533 private static void validatePath(File parent, File child) 534 throws IOException 535 { 536 if (!child.toPath().startsWith(parent.toPath()) ) 537 throw new IOException("Bogus relative path: " + child); 538 if (child.exists()) { 539 // conflict, for now just fail 540 throw new IOException("File " + child + " already exists"); 541 } 542 } 543 544 private static short readHashLength(DataInputStream in) throws IOException { 545 final short hashLength = in.readShort(); 546 ensureNonNegativity(hashLength, "hashLength"); 547 548 return hashLength; 549 } 550 551 private static byte[] readHashBytes(DataInputStream in, short hashLength) 552 throws IOException 553 { 554 final byte[] hash = new byte[hashLength]; 555 in.readFully(hash); 556 557 return hash; 558 } 559 560 private static byte[] readHash(DataInputStream in) throws IOException { 561 return readHashBytes(in, readHashLength(in)); 562 } 563 564 private static byte[] readFileHash(InputStream in) 565 throws IOException 566 { 567 boolean digestStream = in instanceof DigestInputStream ? true : false; 568 DataInputStream din = new DataInputStream(in); 569 570 final short hashLength = readHashLength(din); 571 572 // Turn digest computation off before reading the file hash 573 if (digestStream) 574 ((DigestInputStream)in).on(false); 575 byte[] hash = readHashBytes(din, hashLength); 576 // Turn digest computation on again afterwards. 577 if (digestStream) 578 ((DigestInputStream)in).on(true); 579 580 return hash; 581 } 582 583 /** 584 * <p> A module-file header </p> 585 */ 586 public final static class ModuleFileHeader { 587 private static final int LENGTH_WITHOUT_VARIABLE_FIELDS = 34; 588 589 // Fields are specified as unsigned. Treat signed values as bugs. 590 private final int magic; // MAGIC 591 private final FileConstants.Type type; // Type.MODULE_FILE 592 private final short major; // ModuleFile.MAJOR_VERSION 593 private final short minor; // ModuleFile.MINOR_VERSION 594 private final long csize; // Size of rest of file, compressed 595 private final long usize; // Space required for uncompressed contents 596 // (upper private final ound; need not be exact) 597 private final HashType hashType; // One of ModuleFile.HashType 598 // (applies final o all hashes in this file) 599 private final byte[] hash; // Hash of entire file (except this hash 600 // and the Signature section, if present) 601 private final ModuleArchitecture modArch;// os/arch, Java-modified UTF-8 pair 602 603 private final int length; 604 605 public HashType getHashType() { 606 return hashType; 607 } 608 609 public byte[] getHash() { 610 return hash.clone(); 611 } 612 613 private byte[] getHashNoClone() { 614 return hash; 615 } 616 617 public long getCSize() { 618 return csize; 619 } 620 621 public long getUSize() { 622 return usize; 623 } 624 625 public ModuleArchitecture getArchitecture() { 626 return modArch; 627 } 628 629 public int getLength() { 630 return length; 631 } 632 633 private ModuleFileHeader(long csize, long usize, HashType hashType, 634 byte[] hash, ModuleArchitecture modArch) { 635 ensureNonNegativity(csize, "csize"); 636 ensureNonNegativity(usize, "usize"); 637 638 magic = FileConstants.MAGIC; 639 type = FileConstants.Type.MODULE_FILE; 640 major = MAJOR_VERSION; 641 minor = MINOR_VERSION; 642 643 this.csize = csize; 644 this.usize = usize; 645 this.hashType = hashType; 646 this.hash = hash.clone(); 647 this.modArch = modArch; 648 try { 649 length = LENGTH_WITHOUT_VARIABLE_FIELDS 650 + hash.length 651 + modArch.os().getBytes("UTF-8").length 652 + modArch.arch().getBytes("UTF-8").length; 653 } catch (UnsupportedEncodingException x) { 654 throw new InternalError(x); 655 } 656 } 657 658 public static Builder newBuilder() { 659 return new Builder(); 660 } 661 662 public static class Builder { 663 private long csize; 664 private long usize; 665 private ModuleArchitecture architecture = ModuleArchitecture.ANY; 666 private HashType hashType; 667 private byte[] hash; 668 669 private Builder() {} 670 671 public Builder setHashType(HashType hashType) { 672 this.hashType = hashType; 673 return this; 674 } 675 676 public Builder setHash(byte[] hash) { 677 this.hash = hash.clone(); 678 return this; 679 } 680 681 public Builder setCSize(long csize) { 682 this.csize = csize; 683 return this; 684 } 685 686 public Builder setUSize(long usize) { 687 this.usize = usize; 688 return this; 689 } 690 691 public Builder setArchitecture(ModuleArchitecture architecture) { 692 this.architecture = architecture; 693 return this; 694 } 695 696 public ModuleFileHeader build() { 697 ensureNonNegativity(csize, "csize"); 698 ensureNonNegativity(usize, "usize"); 699 return new ModuleFileHeader(csize, usize, hashType, hash, 700 architecture); 701 } 702 } 703 704 public void write(final DataOutput out) throws IOException { 705 out.writeInt(magic); 706 out.writeShort(type.value()); 707 out.writeShort(major); 708 out.writeShort(minor); 709 out.writeLong(csize); 710 out.writeLong(usize); 711 out.writeShort(hashType.value()); 712 writeHash(out, hash); 713 out.writeUTF(modArch.os()); 714 out.writeUTF(modArch.arch()); 715 } 716 717 public static ModuleFileHeader read(final InputStream in) 718 throws IOException 719 { 720 DataInputStream din = new DataInputStream(in); 721 722 final int magic = din.readInt(); 723 ensureMatch(magic, FileConstants.MAGIC, 724 "FileConstants.MAGIC"); 725 726 final short type = din.readShort(); 727 ensureMatch(type, FileConstants.Type.MODULE_FILE.value(), 728 "Type.MODULE_FILE"); 729 730 final short major = din.readShort(); 731 ensureMatch(major, MAJOR_VERSION, 732 "ModuleFile.MAJOR_VERSION"); 733 734 final short minor = din.readShort(); 735 ensureMatch(minor, MINOR_VERSION, 736 "ModuleFile.MINOR_VERSION"); 737 738 final long csize = din.readLong(); 739 final long usize = din.readLong(); 740 741 final short hashTypeValue = din.readShort(); 742 HashType hashType = null; 743 try { 744 hashType = HashType.valueOf(hashTypeValue); 745 } catch (IllegalArgumentException x) { 746 throw new IOException("Invalid hash type: " + hashTypeValue); 747 } 748 final byte[] hash = readFileHash(in); 749 750 final String os = din.readUTF(); 751 final String arch = din.readUTF(); 752 final ModuleArchitecture architecture = ModuleArchitecture.create(os, arch); 753 754 return new ModuleFileHeader(csize, usize, hashType, hash, architecture); 755 } 756 757 @Override 758 public String toString() { 759 return "MODULE{csize=" + csize + 760 ", hash=" + hashHexString(hash) + "}"; 761 } 762 } 763 764 /** 765 * <p> A module-file section header </p> 766 */ 767 public final static class SectionHeader { 768 public static final int LENGTH_WITHOUT_HASH = 12; 769 public static final int LENGTH = 770 LENGTH_WITHOUT_HASH + HashType.SHA256.length(); 771 772 // Fields are specified as unsigned. Treat signed values as bugs. 773 private final SectionType type; 774 private final Compressor compressor; 775 private final int csize; // Size of section content, compressed 776 private final short subsections; // Number of following subsections 777 private final byte[] hash; // Hash of section content 778 779 public SectionHeader(SectionType type, 780 Compressor compressor, 781 int csize, short subsections, byte[] hash) { 782 ensureNonNull(type, "type"); 783 ensureNonNull(compressor, "compressor"); 784 ensureNonNegativity(csize, "csize"); 785 ensureNonNegativity(subsections, "subsections"); 786 ensureNonNull(hash, "hash"); 787 checkSubsectionCount(type, subsections); 788 checkCompressor(type, compressor); 789 790 this.type = type; 791 this.compressor = compressor; 792 this.csize = csize; 793 this.subsections = subsections; 794 this.hash = hash.clone(); 795 } 796 797 public void write(DataOutput out) throws IOException { 798 out.writeShort(type.value()); 799 out.writeShort(compressor.value()); 800 out.writeInt(csize); 801 out.writeShort(subsections); 802 writeHash(out, hash); 803 } 804 805 private static SectionType lookupSectionType(short value) { 806 for (SectionType i : SectionType.class.getEnumConstants()) { 807 if (i.value() == value) return i; 808 } 809 810 throw new 811 IllegalArgumentException("No SectionType exists with value " 812 + value); 813 } 814 815 private static Compressor lookupCompressor(short value) { 816 for (Compressor i : Compressor.class.getEnumConstants()) { 817 if (i.value() == value) return i; 818 } 819 820 throw new 821 IllegalArgumentException("No Compressor exists with value " 822 + value); 823 } 824 825 public static SectionHeader read(DataInputStream in) throws IOException { 826 short tvalue = in.readShort(); 827 final SectionType type = lookupSectionType(tvalue); 828 short cvalue = in.readShort(); 829 final Compressor compressor = lookupCompressor(cvalue); 830 final int csize = in.readInt(); 831 final short sections = in.readShort(); 832 final byte[] hash = readHash(in); 833 834 return new SectionHeader(type, compressor, csize, 835 sections, hash); 836 } 837 838 public SectionType getType() { 839 return type; 840 } 841 842 public Compressor getCompressor() { 843 return compressor; 844 } 845 846 public int getCSize() { 847 return csize; 848 } 849 850 public short getSubsections() { 851 return subsections; 852 } 853 854 public byte[] getHash() { 855 return hash.clone(); 856 } 857 858 private byte[] getHashNoClone() { 859 return hash; 860 } 861 862 @Override 863 public String toString() { 864 return "SectionHeader{type= " + type 865 + ", compressor=" + compressor 866 + ", csize=" + csize 867 + ", subsections=" + subsections 868 + ", hash=" + hashHexString(hash) + "}"; 869 } 870 } 871 872 /** 873 * <p> A module-file sub-section header </p> 874 */ 875 public final static class SubSectionFileHeader { 876 private final int csize; // Size of file, compressed 877 private final String path; // Path name, in Java-modified UTF-8 878 879 public int getCSize() { 880 return csize; 881 } 882 883 public String getPath() { 884 return path; 885 } 886 887 public SubSectionFileHeader(int csize, String path) { 888 ensureNonNegativity(csize, "csize"); 889 ensureNonNull(path, "path"); 890 891 this.csize = csize; 892 this.path = path; 893 } 894 895 public void write(DataOutput out) throws IOException { 896 out.writeShort(SubSectionType.FILE.value()); 897 out.writeInt(csize); 898 out.writeUTF(path); 899 } 900 901 public static SubSectionFileHeader read(DataInputStream in) 902 throws IOException 903 { 904 final short type = in.readShort(); 905 ensureMatch(type, SubSectionType.FILE.value(), 906 "ModuleFile.SubSectionType.FILE"); 907 final int csize = in.readInt(); 908 final String path = in.readUTF(); 909 910 return new SubSectionFileHeader(csize, path); 911 } 912 } 913 914 /** 915 * <p> A module-file signature section </p> 916 */ 917 public final static class SignatureSection { 918 public static final int HEADER_LENGTH = 6; // type + signature length 919 920 private final int signatureType; // One of FileConstants.ModuleFile.HashType 921 private final int signatureLength; // Length of signature 922 private final byte[] signature; // Signature bytes 923 924 public int getSignatureType() { 925 return signatureType; 926 } 927 928 public int getSignatureLength() { 929 return signatureLength; 930 } 931 932 public byte[] getSignature() { 933 return signature; 934 } 935 936 public SignatureSection(int signatureType, int signatureLength, 937 byte[] signature) { 938 ensureNonNegativity(signatureLength, "signatureLength"); 939 940 this.signatureType = signatureType; 941 this.signatureLength = signatureLength; 942 this.signature = signature.clone(); 943 } 944 945 public void write(DataOutput out) throws IOException { 946 out.writeShort(signatureType); 947 out.writeInt(signatureLength); 948 out.write(signature); 949 } 950 951 public static SignatureSection read(DataInputStream in) 952 throws IOException 953 { 954 short signatureType = in.readShort(); 955 try { 956 SignatureType.valueOf(signatureType); 957 } catch (IllegalArgumentException x) { 958 throw new IOException("Invalid signature type: " + signatureType); 959 } 960 final int signatureLength = in.readInt(); 961 ensureNonNegativity(signatureLength, "signatureLength"); 962 final byte[] signature = new byte[signatureLength]; 963 in.readFully(signature); 964 return new SignatureSection(signatureType, signatureLength, 965 signature); 966 } 967 } 968 969 private static void writeHash(DataOutput out, byte[] hash) 970 throws IOException 971 { 972 out.writeShort(hash.length); 973 out.write(hash); 974 } 975 976 } --- EOF ---