1 /*
   2  * Copyright (c) 2016, 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 jdk.internal.jmod;
  27 
  28 import java.io.BufferedInputStream;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.OutputStream;
  32 import java.nio.file.Files;
  33 import java.nio.file.Path;
  34 import java.util.Iterator;
  35 import java.util.stream.Stream;
  36 import java.util.zip.ZipEntry;
  37 import java.util.zip.ZipFile;
  38 
  39 /**
  40  * Helper class to read JMOD file
  41  */
  42 public class JmodFile implements AutoCloseable {
  43     // jmod magic number and version number
  44     private static final int JMOD_MAJOR_VERSION = 0x01;
  45     private static final int JMOD_MINOR_VERSION = 0x00;
  46     private static final byte[] JMOD_MAGIC_NUMBER = {
  47         0x4A, 0x4D, /* JM */
  48         JMOD_MAJOR_VERSION, JMOD_MINOR_VERSION, /* version 1.0 */
  49     };
  50 
  51     public static void checkMagic(Path file) throws IOException {
  52         try (InputStream in = Files.newInputStream(file);
  53              BufferedInputStream bis = new BufferedInputStream(in)) {
  54             // validate the header
  55             byte[] magic = new byte[4];
  56             bis.read(magic);
  57             if (magic[0] != JMOD_MAGIC_NUMBER[0] ||
  58                 magic[1] != JMOD_MAGIC_NUMBER[1]) {
  59                 throw new IOException("Invalid jmod file: " + file.toString());
  60             }
  61             if (magic[2] > JMOD_MAJOR_VERSION ||
  62                 (magic[2] == JMOD_MAJOR_VERSION && magic[3] > JMOD_MINOR_VERSION)) {
  63                 throw new IOException("Unsupported jmod version: " +
  64                     magic[2] + "." + magic[3] + " in " + file.toString());
  65             }
  66         }
  67     }
  68 
  69     /**
  70      * JMOD sections
  71      */
  72     public static enum Section {
  73         NATIVE_LIBS("native"),
  74         NATIVE_CMDS("bin"),
  75         CLASSES("classes"),
  76         CONFIG("conf"),
  77         HEADER_FILES("include"),
  78         MAN_PAGES("man");
  79 
  80         private final String jmodDir;
  81         private Section(String jmodDir) {
  82             this.jmodDir = jmodDir;
  83         }
  84 
  85         /**
  86          * Returns the directory name in the JMOD file corresponding to
  87          * this section
  88          */
  89         public String jmodDir() { return jmodDir; }
  90 
  91     }
  92 
  93     /**
  94      * JMOD file entry.
  95      *
  96      * Each entry corresponds to a ZipEntry whose name is:
  97      *   Section::jmodDir + '/' + name
  98      */
  99     public static class Entry {
 100         private final ZipEntry zipEntry;
 101         private final Section section;
 102         private final String name;
 103 
 104         private Entry(ZipEntry e) {
 105             String name = e.getName();
 106             int i = name.indexOf('/');
 107             if (i <= 1) {
 108                 throw new RuntimeException("invalid jmod entry: " + name);
 109             }
 110 
 111             this.zipEntry = e;
 112             this.section = section(name);
 113             this.name = name.substring(i+1);
 114         }
 115 
 116         /**
 117          * Returns the section of this entry.
 118          */
 119         public Section section() {
 120             return section;
 121         }
 122 
 123         /**
 124          * Returns the name of this entry.
 125          */
 126         public String name() {
 127             return name;
 128         }
 129 
 130         /**
 131          * Returns the size of this entry.
 132          */
 133         public long size() {
 134             return zipEntry.getSize();
 135         }
 136 
 137         public ZipEntry zipEntry() {
 138             return zipEntry;
 139         }
 140 
 141         @Override
 142         public String toString() {
 143             return section.jmodDir() + "/" + name;
 144         }
 145 
 146         static Section section(String name) {
 147             int i = name.indexOf('/');
 148             String s = name.substring(0, i);
 149             switch (s) {
 150                 case "native":
 151                     return Section.NATIVE_LIBS;
 152                 case "bin":
 153                     return Section.NATIVE_CMDS;
 154                 case "classes":
 155                     return Section.CLASSES;
 156                 case "conf":
 157                     return Section.CONFIG;
 158                 case "include":
 159                     return Section.HEADER_FILES;
 160                 case "man":
 161                     return Section.MAN_PAGES;
 162                 default:
 163                     throw new IllegalArgumentException("invalid section: " + s);
 164             }
 165         }
 166     }
 167 
 168     private final Path file;
 169     private final ZipFile zipfile;
 170 
 171     /**
 172      * Constructs a {@code JmodFile} from a given path.
 173      */
 174     public JmodFile(Path file) throws IOException {
 175         checkMagic(file);
 176         this.file = file;
 177         this.zipfile = new ZipFile(file.toFile());
 178     }
 179 
 180     public static void writeMagicNumber(OutputStream os) throws IOException {
 181         os.write(JMOD_MAGIC_NUMBER);
 182     }
 183 
 184     /**
 185      * Returns the {@code Entry} for a resource in a JMOD file section
 186      * or {@code null} if not found.
 187      */
 188     public Entry getEntry(Section section, String name) {
 189         String entry = section.jmodDir() + "/" + name;
 190         ZipEntry ze = zipfile.getEntry(entry);
 191         return (ze != null) ? new Entry(ze) : null;
 192     }
 193 
 194     /**
 195      * Opens an {@code InputStream} for reading the named entry of the given
 196      * section in this jmod file.
 197      *
 198      * @throws IOException if the named entry is not found, or I/O error
 199      *         occurs when reading it
 200      */
 201     public InputStream getInputStream(Section section, String name)
 202         throws IOException
 203     {
 204         String entry = section.jmodDir() + "/" + name;
 205         ZipEntry e = zipfile.getEntry(entry);
 206         if (e == null) {
 207             throw new IOException(name + " not found: " + file);
 208         }
 209         return zipfile.getInputStream(e);
 210     }
 211 
 212     /**
 213      * Opens an {@code InputStream} for reading an entry in the JMOD file.
 214      *
 215      * @throws IOException if an I/O error occurs
 216      */
 217     public InputStream getInputStream(Entry entry) throws IOException {
 218         return zipfile.getInputStream(entry.zipEntry());
 219     }
 220 
 221     /**
 222      * Returns a stream of non-directory entries in this jmod file.
 223      */
 224     public Stream<Entry> stream() {
 225         return zipfile.stream()
 226                       .filter(e -> !e.isDirectory())
 227                       .map(Entry::new);
 228     }
 229 
 230     @Override
 231     public void close() throws IOException {
 232         if (zipfile != null) {
 233             zipfile.close();
 234         }
 235     }
 236 }