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