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