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 List<byte[]> getCalculatedHashes() {
 222             List<byte[]> hashes = new ArrayList<>();
 223             hashes.add(parser.getHeaderHash());
 224             for (Entry<SectionType,byte[]> entry : parser.getHashes().entrySet()) {
 225                 if (entry.getKey() != SIGNATURE)
 226                     hashes.add(entry.getValue());
 227             }
 228             hashes.add(getHash());
 229 
 230             return hashes;
 231         }
 232 
 233         public boolean hasSignature() {
 234             return moduleSignatureBytes != null;
 235         }
 236 
 237         public SignatureType getSignatureType() {
 238             return moduleSignatureType;
 239         }
 240 
 241         public byte[] getSignature() {
 242             return moduleSignatureBytes == null ? null :
 243                    moduleSignatureBytes.clone();
 244         }
 245 
 246         /*package*/ byte[] getSignatureNoClone() {
 247             return moduleSignatureBytes;
 248         }
 249 
 250         public void close() throws IOException {
 251             try {
 252                 if (contentStream != null) {
 253                     contentStream.close();
 254                     contentStream = null;
 255                 }
 256             } finally {
 257                 if (filesWriter != null) {
 258                     filesWriter.close();
 259                     filesWriter = null;
 260                 }
 261             }
 262         }
 263 
 264         // subsections/files (resources, libs, cmds, configs)
 265         public void readSubSection(SectionType type) throws IOException {
 266             assert type == RESOURCES || type == NATIVE_LIBS ||
 267                    type == NATIVE_CMDS || type == CONFIG;
 268 
 269             SubSectionFileHeader subHeader = parser.getSubSectionFileHeader();
 270             String path = subHeader.getPath();
 271             try (OutputStream sink = openOutputStream(type, path)) {
 272                 copyStream(parser.getContentStream(), sink);
 273             }
 274 
 275             // post processing for executable and files outside the module dir
 276             postExtract(type, currentPath);
 277         }
 278 
 279         private JarOutputStream contentStream = null;
 280 
 281         private JarOutputStream contentStream() throws IOException {
 282             if (contentStream != null)
 283                 return contentStream;
 284 
 285             return contentStream = new JarOutputStream(
 286                     new BufferedOutputStream(
 287                         new FileOutputStream(computeRealPath("classes"))));
 288         }
 289 
 290         private File currentPath = null;
 291 
 292         private OutputStream openOutputStream(SectionType type, String path)
 293             throws IOException
 294         {
 295             currentPath = null;
 296             if (type == CLASSES || type == RESOURCES)
 297                 return Files.newOutputStream(contentStream(), deflate, path);
 298             currentPath = computeRealPath(type, path);
 299             File parent = currentPath.getParentFile();
 300             if (!parent.exists())
 301                 Files.mkdirs(parent, currentPath.getName());
 302             return new BufferedOutputStream(new FileOutputStream(currentPath));
 303         }
 304 
 305         // Track files installed outside the module library. For later removal.
 306         // files are relative to the modules directory.
 307         private PrintWriter filesWriter;
 308 
 309         private void trackFiles(File file)
 310             throws IOException
 311         {
 312             if (file == null || file.toPath().startsWith(destination.toPath()))
 313                 return;
 314 
 315             // Lazy construction, not all modules will need this.
 316             if (filesWriter == null)
 317                 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
 318 
 319             filesWriter.println(Files.convertSeparator(relativize(destination, file)));
 320             filesWriter.flush();
 321         }
 322 
 323         List<IOException> remove() {
 324             return ModuleFile.Reader.remove(destination);
 325         }
 326 
 327         // Removes a module, given its module install directory
 328         static List<IOException> remove(File moduleDir) {
 329             List<IOException> excs = new ArrayList<>();
 330             // Firstly remove any files installed outside of the module dir
 331             File files = new File(moduleDir, "files");
 332             if (files.exists()) {
 333                 try {
 334                     List<String> filenames =
 335                         java.nio.file.Files.readAllLines(files.toPath(),
 336                                                          Charset.forName("UTF-8"));
 337                     for (String fn : filenames) {
 338                         try {
 339                             Files.delete(new File(moduleDir,
 340                                                   Files.platformSeparator(fn)));
 341                         } catch (IOException x) {
 342                             excs.add(x);
 343                         }
 344                     }
 345                 } catch (IOException x) {
 346                     excs.add(x);
 347                 }
 348             }
 349 
 350             excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
 351             return excs;
 352         }
 353 
 354         // Returns the absolute path of the given section type.
 355         private File getDirOfSection(SectionType type) {
 356             if (type == NATIVE_LIBS)
 357                 return natlibs;
 358             else if (type == NATIVE_CMDS)
 359                 return natcmds;
 360             else if (type == CONFIG)
 361                 return configs;
 362 
 363             // resolve sub dir section paths against the modules directory
 364             return new File(destination, ModuleFile.getSubdirOfSection(type));
 365         }
 366 
 367         private File computeRealPath(String path) throws IOException {
 368             return resolveAndNormalize(destination, path);
 369         }
 370 
 371         private File computeRealPath(SectionType type, String storedpath)
 372             throws IOException
 373         {
 374             File sectionPath = getDirOfSection(type);
 375             File realpath = new File(sectionPath,
 376                  Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
 377 
 378             validatePath(sectionPath, realpath);
 379 
 380             // Create the parent directories if necessary
 381             File parent = realpath.getParentFile();
 382             if (!parent.exists())
 383                 Files.mkdirs(parent, realpath.getName());
 384 
 385             return realpath;
 386         }
 387 
 388         private static void markNativeCodeExecutable(SectionType type,
 389                                                      File file)
 390         {
 391             if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
 392                     System.getProperty("os.name").startsWith("Windows")))
 393                 file.setExecutable(true);
 394         }
 395 
 396         private void postExtract(SectionType type, File path)
 397             throws IOException
 398         {
 399             markNativeCodeExecutable(type, path);
 400             trackFiles(path);
 401         }
 402     }
 403 
 404     private static void checkCompressor(SectionType type,
 405                                         Compressor compressor) {
 406 
 407         if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
 408             (CLASSES == type && Compressor.PACK200_GZIP != compressor))
 409             throw new IllegalArgumentException(type
 410                                                + " may not use compressor "
 411                                                + compressor);
 412     }
 413 
 414     private static void checkSubsectionCount(SectionType type,
 415                                              short subsections) {
 416         if (!type.hasFiles() && subsections != 0)
 417             throw new IllegalArgumentException(type
 418                                                + " subsection count not 0: "
 419                                                + subsections);
 420         else if (type.hasFiles() && subsections == 0)
 421             throw new IllegalArgumentException(type + " subsection count is 0");
 422     }
 423 
 424     private static void copyStream(InputStream in, OutputStream out)
 425         throws IOException
 426     {
 427         byte[] buf = new byte[8192];
 428         int read;
 429         while ((read = in.read(buf)) > 0)
 430             out.write(buf, 0, read);
 431     }
 432 
 433     private static void ensureNonNegativity(long size, String parameter) {
 434         if (size < 0)
 435             throw new IllegalArgumentException(parameter + "<0: " + size);
 436     }
 437 
 438     private static void ensureNonNull(Object reference, String parameter) {
 439         if (null == reference)
 440             throw new IllegalArgumentException(parameter + " == null");
 441     }
 442 
 443     private static void ensureMatch(int found, int expected, String field)
 444         throws IOException
 445     {
 446         if (found != expected)
 447             throw new IOException(field + " expected : "
 448                 + Integer.toHexString(expected) + " found: "
 449                 + Integer.toHexString(found));
 450     }
 451 
 452     private static void ensureShortNativePath(File path, String name)
 453         throws IOException
 454     {
 455             // TODO: check for native code file in a stricter way
 456             if (path.canExecute()
 457                 && name.indexOf('/') != -1)
 458                 throw new IOException("Native code path too long: " + path);
 459     }
 460 
 461     private static void ensureValidFileSize(long size, File path)
 462         throws IOException
 463     {
 464         if (size < 0 || size > Integer.MAX_VALUE)
 465             throw new IOException("File " + path + " too large: " + size);
 466     }
 467 
 468     static MessageDigest getHashInstance(HashType hashtype)
 469         throws IOException
 470     {
 471         try {
 472             switch(hashtype) {
 473             case SHA256:
 474                 return MessageDigest.getInstance("SHA-256");
 475             default:
 476                 throw new IOException("Unknown hash type: " + hashtype);
 477             }
 478         }
 479         catch (NoSuchAlgorithmException ex) {
 480             throw new IOException(hashtype + " not found", ex);
 481         }
 482     }
 483 
 484     private static short getMUTF8Length(String name) {
 485         short size = 2;
 486 
 487         for (int i = name.length()-1; i >= 0; i--) {
 488             char ch = name.charAt(i);
 489 
 490             if ('\u0001' <= ch && ch <= '\u007F')
 491                 size += 1;
 492             else if ('\u0000' == ch
 493                      || '\u0080' <= ch && ch <= '\u07FF')
 494                 size += 2;
 495             else
 496                 size += 3;
 497         }
 498 
 499         return size;
 500     }
 501 
 502     private static String hashHexString(byte[] hash) {
 503         StringBuilder hex = new StringBuilder("0x");
 504         for (int i = 0; i < hash.length; i++) {
 505             int val = (hash[i] & 0xFF);
 506             if (val <= 16)
 507                 hex.append("0");
 508             hex.append(Integer.toHexString(val));
 509         }
 510         return hex.toString();
 511     }
 512 
 513     private static File resolveAndNormalize(File directory, String path)
 514         throws IOException
 515     {
 516         File realpath = new File(directory, path);
 517         if (directory != null &&
 518             ! realpath.toPath().startsWith(directory.toPath()))
 519             throw new IOException("Bogus relative path: " + path);
 520 
 521         return realpath;
 522     }
 523 
 524 
 525     private static String relativize(File directory, File path) throws IOException {
 526         return (directory.toPath().relativize(path.toPath().toRealPath())).toString();
 527     }
 528 
 529     private static void validatePath(File parent, File child)
 530         throws IOException
 531     {
 532         if (!child.toPath().startsWith(parent.toPath()) )
 533             throw new IOException("Bogus relative path: " + child);
 534         if (child.exists()) {
 535             // conflict, for now just fail
 536             throw new IOException("File " + child + " already exists");
 537         }
 538     }
 539 
 540     private static short readHashLength(DataInputStream in) throws IOException {
 541         final short hashLength = in.readShort();
 542         ensureNonNegativity(hashLength, "hashLength");
 543 
 544         return hashLength;
 545     }
 546 
 547     private static byte[] readHashBytes(DataInputStream in, short hashLength)
 548         throws IOException
 549     {
 550         final byte[] hash = new byte[hashLength];
 551         in.readFully(hash);
 552 
 553         return hash;
 554     }
 555 
 556     private static byte[] readHash(DataInputStream in) throws IOException {
 557         return readHashBytes(in, readHashLength(in));
 558     }
 559 
 560     private static byte[] readFileHash(DigestInputStream dis)
 561         throws IOException
 562     {
 563         DataInputStream in = new DataInputStream(dis);

 564 
 565         final short hashLength = readHashLength(in);
 566 
 567         // Turn digest computation off before reading the file hash
 568         dis.on(false);
 569         byte[] hash = readHashBytes(in, hashLength);

 570         // Turn digest computation on again afterwards.
 571         dis.on(true);

 572 
 573         return hash;
 574     }
 575 
 576     /**
 577      * <p> A module-file header </p>
 578      */
 579     public final static class ModuleFileHeader {
 580         public static final int LENGTH_WITHOUT_HASH = 30;
 581         public static final int LENGTH =
 582             LENGTH_WITHOUT_HASH + HashType.SHA256.length();
 583 
 584         // Fields are specified as unsigned. Treat signed values as bugs.
 585         private final int magic;                // MAGIC
 586         private final FileConstants.Type type;  // Type.MODULE_FILE
 587         private final short major;              // ModuleFile.MAJOR_VERSION
 588         private final short minor;              // ModuleFile.MINOR_VERSION
 589         private final long csize;               // Size of rest of file, compressed
 590         private final long usize;               // Space required for uncompressed contents
 591                                                 //   (upper private final ound; need not be exact)
 592         private final HashType hashType;        // One of ModuleFile.HashType
 593                                                 //   (applies final o all hashes in this file)
 594         private final byte[] hash;              // Hash of entire file (except this hash
 595                                                 // and the Signature section, if present)

 596 


 597         public HashType getHashType() {
 598             return hashType;
 599         }
 600         
 601         public byte[] getHash() {
 602             return hash.clone();
 603         }
 604 
 605         private byte[] getHashNoClone() {
 606             return hash;
 607         }
 608 
 609         public long getCSize() {
 610             return csize;
 611         }
 612 
 613         public long getUSize() {
 614             return usize;
 615         }
 616 
 617         public ModuleFileHeader(long csize, long usize,
 618                                 HashType hashType, byte[] hash) {








 619             ensureNonNegativity(csize, "csize");
 620             ensureNonNegativity(usize, "usize");
 621 
 622             magic = FileConstants.MAGIC;
 623             type = FileConstants.Type.MODULE_FILE;
 624             major = MAJOR_VERSION;
 625             minor = MINOR_VERSION;
 626 
 627             this.csize = csize;
 628             this.usize = usize;
 629             this.hashType = hashType;
 630             this.hash = hash.clone();








 631         }

 632 














































 633         public void write(final DataOutput out) throws IOException {
 634             out.writeInt(magic);
 635             out.writeShort(type.value());
 636             out.writeShort(major);
 637             out.writeShort(minor);
 638             out.writeLong(csize);
 639             out.writeLong(usize);
 640             out.writeShort(hashType.value());
 641             writeHash(out, hash);


 642         }
 643 
 644         public static ModuleFileHeader read(final DigestInputStream dis)
 645                 throws IOException
 646         {
 647             DataInputStream in = new DataInputStream(dis);
 648 
 649             final int magic = in.readInt();
 650             ensureMatch(magic, FileConstants.MAGIC,
 651                         "FileConstants.MAGIC");
 652 
 653             final short type = in.readShort();
 654             ensureMatch(type, FileConstants.Type.MODULE_FILE.value(),
 655                        "Type.MODULE_FILE");
 656 
 657             final short major = in.readShort();
 658             ensureMatch(major, MAJOR_VERSION,
 659                         "ModuleFile.MAJOR_VERSION");
 660 
 661             final short minor = in.readShort();
 662             ensureMatch(minor, MINOR_VERSION,
 663                         "ModuleFile.MINOR_VERSION");
 664 
 665             final long csize = in.readLong();
 666             final long usize = in.readLong();
 667             final short hashTypeValue = in.readShort();

 668             HashType hashType = null;
 669             try {
 670                 hashType = HashType.valueOf(hashTypeValue);
 671             } catch (IllegalArgumentException x) {
 672                 throw new IOException("Invalid hash type: " + hashTypeValue);
 673             }
 674             final byte[] hash = readFileHash(dis);
 675 
 676             return new ModuleFileHeader(csize, usize, hashType, hash);




 677         }
 678 
 679         @Override
 680         public String toString() {
 681             return "MODULE{csize=" + csize +
 682                    ", hash=" + hashHexString(hash) + "}";
 683         }
 684     }
 685 
 686     /**
 687      * <p> A module-file section header </p>
 688      */
 689     public final static class SectionHeader {
 690         public static final int LENGTH_WITHOUT_HASH = 12;
 691         public static final int LENGTH =
 692             LENGTH_WITHOUT_HASH + HashType.SHA256.length();
 693 
 694         // Fields are specified as unsigned. Treat signed values as bugs.
 695         private final SectionType type;
 696         private final Compressor compressor;
 697         private final int csize;               // Size of section content, compressed
 698         private final short subsections;       // Number of following subsections
 699         private final byte[] hash;             // Hash of section content
 700 
 701         public SectionHeader(SectionType type,
 702                              Compressor compressor,
 703                              int csize, short subsections, byte[] hash) {
 704             ensureNonNull(type, "type");
 705             ensureNonNull(compressor, "compressor");
 706             ensureNonNegativity(csize, "csize");
 707             ensureNonNegativity(subsections, "subsections");
 708             ensureNonNull(hash, "hash");
 709             checkSubsectionCount(type, subsections);
 710             checkCompressor(type, compressor);
 711 
 712             this.type = type;
 713             this.compressor = compressor;
 714             this.csize = csize;
 715             this.subsections = subsections;
 716             this.hash = hash.clone();
 717         }
 718 
 719         public void write(DataOutput out) throws IOException {
 720             out.writeShort(type.value());
 721             out.writeShort(compressor.value());
 722             out.writeInt(csize);
 723             out.writeShort(subsections);
 724             writeHash(out, hash);
 725         }
 726 
 727         private static SectionType lookupSectionType(short value) {
 728             for (SectionType i : SectionType.class.getEnumConstants()) {
 729                 if (i.value() == value) return i;
 730             }
 731 
 732             throw new
 733                 IllegalArgumentException("No SectionType exists with value "
 734                                          + value);
 735         }
 736 
 737         private static Compressor lookupCompressor(short value) {
 738             for (Compressor i : Compressor.class.getEnumConstants()) {
 739                 if (i.value() == value) return i;
 740             }
 741 
 742             throw new
 743                 IllegalArgumentException("No Compressor exists with value "
 744                                          + value);
 745         }
 746 
 747         public static SectionHeader read(DataInputStream in) throws IOException {
 748             short tvalue = in.readShort();
 749             final SectionType type = lookupSectionType(tvalue);
 750             short cvalue = in.readShort();
 751             final Compressor compressor = lookupCompressor(cvalue);
 752             final int csize = in.readInt();
 753             final short sections = in.readShort();
 754             final byte[] hash = readHash(in);
 755 
 756             return new SectionHeader(type, compressor, csize,
 757                     sections, hash);
 758         }
 759 
 760         public SectionType getType() {
 761             return type;
 762         }
 763 
 764         public Compressor getCompressor() {
 765             return compressor;
 766         }
 767 
 768         public int getCSize() {
 769             return csize;
 770         }
 771 
 772         public short getSubsections() {
 773             return subsections;
 774         }
 775 
 776         public byte[] getHash() {
 777             return hash.clone();
 778         }
 779 
 780         private byte[] getHashNoClone() {
 781             return hash;
 782         }
 783 
 784         @Override
 785         public String toString() {
 786             return "SectionHeader{type= " + type
 787                     + ", compressor=" + compressor
 788                     + ", csize=" + csize
 789                     + ", subsections=" + subsections
 790                     + ", hash=" + hashHexString(hash) + "}";
 791         }
 792     }
 793 
 794     /**
 795      * <p> A module-file sub-section header </p>
 796      */
 797     public final static class SubSectionFileHeader {
 798         private final int csize;              // Size of file, compressed
 799         private final String path;            // Path name, in Java-modified UTF-8
 800 
 801         public int getCSize() {
 802             return csize;
 803         }
 804 
 805         public String getPath() {
 806             return path;
 807         }
 808 
 809         public SubSectionFileHeader(int csize, String path) {
 810             ensureNonNegativity(csize, "csize");
 811             ensureNonNull(path, "path");
 812 
 813             this.csize = csize;
 814             this.path = path;
 815         }
 816 
 817         public void write(DataOutput out) throws IOException {
 818             out.writeShort(SubSectionType.FILE.value());
 819             out.writeInt(csize);
 820             out.writeUTF(path);
 821         }
 822 
 823         public static SubSectionFileHeader read(DataInputStream in)
 824                 throws IOException
 825         {
 826             final short type = in.readShort();
 827             ensureMatch(type, SubSectionType.FILE.value(),
 828                         "ModuleFile.SubSectionType.FILE");
 829             final int csize = in.readInt();
 830             final String path = in.readUTF();
 831 
 832             return new SubSectionFileHeader(csize, path);
 833         }
 834     }
 835 
 836     /**
 837      * <p> A module-file signature section </p>
 838      */
 839     public final static class SignatureSection {
 840         public static final int HEADER_LENGTH = 6; // type + signature length
 841 
 842         private final int signatureType;   // One of FileConstants.ModuleFile.HashType
 843         private final int signatureLength; // Length of signature
 844         private final byte[] signature;    // Signature bytes
 845 
 846         public int getSignatureType() {
 847             return signatureType;
 848         }
 849 
 850         public int getSignatureLength() {
 851             return signatureLength;
 852         }
 853 
 854         public byte[] getSignature() {
 855             return signature;
 856         }
 857 
 858         public SignatureSection(int signatureType, int signatureLength,
 859                                 byte[] signature) {
 860             ensureNonNegativity(signatureLength, "signatureLength");
 861 
 862             this.signatureType = signatureType;
 863             this.signatureLength = signatureLength;
 864             this.signature = signature.clone();
 865         }
 866 
 867         public void write(DataOutput out) throws IOException {
 868             out.writeShort(signatureType);
 869             out.writeInt(signatureLength);
 870             out.write(signature);
 871         }
 872 
 873         public static SignatureSection read(DataInputStream in)
 874             throws IOException
 875         {
 876             short signatureType = in.readShort();
 877             try {
 878                 SignatureType.valueOf(signatureType);
 879             } catch (IllegalArgumentException x) {
 880                 throw new IOException("Invalid signature type: " + signatureType);
 881             }
 882             final int signatureLength = in.readInt();
 883             ensureNonNegativity(signatureLength, "signatureLength");
 884             final byte[] signature = new byte[signatureLength];
 885             in.readFully(signature);
 886             return new SignatureSection(signatureType, signatureLength,
 887                                         signature);
 888         }
 889     }
 890 
 891     private static void writeHash(DataOutput out, byte[] hash)
 892             throws IOException
 893     {
 894         out.writeShort(hash.length);
 895         out.write(hash);
 896     }
 897 
 898 }
--- EOF ---