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 }