1 /* 2 * Copyright (c) 2003, 2013, 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 com.sun.java.util.jar.pack; 27 28 import java.beans.PropertyChangeListener; 29 import java.io.BufferedInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.File; 32 import java.io.FileInputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.OutputStream; 36 import java.util.HashSet; 37 import java.util.Set; 38 import java.util.SortedMap; 39 import java.util.TimeZone; 40 import java.util.jar.JarEntry; 41 import java.util.jar.JarInputStream; 42 import java.util.jar.JarOutputStream; 43 import java.util.jar.Pack200; 44 import java.util.zip.CRC32; 45 import java.util.zip.CheckedOutputStream; 46 import java.util.zip.ZipEntry; 47 48 /* 49 * Implementation of the Pack provider. 50 * </pre></blockquote> 51 * @author John Rose 52 * @author Kumar Srinivasan 53 */ 54 55 56 public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker { 57 58 59 /** 60 * Register a listener for changes to options. 61 * @param listener An object to be invoked when a property is changed. 62 */ 63 public void addPropertyChangeListener(PropertyChangeListener listener) { 64 props.addListener(listener); 65 } 66 67 68 /** 69 * Remove a listener for the PropertyChange event. 70 * @param listener The PropertyChange listener to be removed. 71 */ 72 public void removePropertyChangeListener(PropertyChangeListener listener) { 73 props.removeListener(listener); 74 } 75 76 public UnpackerImpl() {} 77 78 79 80 /** 81 * Get the set of options for the pack and unpack engines. 82 * @return A sorted association of option key strings to option values. 83 */ 84 public SortedMap<String, String> properties() { 85 return props; 86 } 87 88 // Back-pointer to NativeUnpacker, when active. 89 Object _nunp; 90 91 92 public String toString() { 93 return Utils.getVersionString(); 94 } 95 96 //Driver routines 97 98 // The unpack worker... 99 /** 100 * Takes a packed-stream InputStream, and writes to a JarOutputStream. Internally 101 * the entire buffer must be read, it may be more efficient to read the packed-stream 102 * to a file and pass the File object, in the alternate method described below. 103 * <p> 104 * Closes its input but not its output. (The output can accumulate more elements.) 105 * @param in an InputStream. 106 * @param out a JarOutputStream. 107 * @exception IOException if an error is encountered. 108 */ 109 public synchronized void unpack(InputStream in, JarOutputStream out) throws IOException { 110 if (in == null) { 111 throw new NullPointerException("null input"); 112 } 113 if (out == null) { 114 throw new NullPointerException("null output"); 115 } 116 assert(Utils.currentInstance.get() == null); 117 TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) 118 ? null 119 : TimeZone.getDefault(); 120 121 try { 122 Utils.currentInstance.set(this); 123 if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 124 final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); 125 BufferedInputStream in0 = new BufferedInputStream(in); 126 if (Utils.isJarMagic(Utils.readMagic(in0))) { 127 if (verbose > 0) 128 Utils.log.info("Copying unpacked JAR file..."); 129 Utils.copyJarFile(new JarInputStream(in0), out); 130 } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) { 131 (new DoUnpack()).run(in0, out); 132 in0.close(); 133 Utils.markJarFile(out); 134 } else { 135 try { 136 (new NativeUnpack(this)).run(in0, out); 137 in0.close(); 138 Utils.markJarFile(out); 139 } catch (UnsatisfiedLinkError ule) { 140 // failover to java implementation 141 (new DoUnpack()).run(in0, out); 142 in0.close(); 143 Utils.markJarFile(out); 144 } 145 } 146 } finally { 147 _nunp = null; 148 Utils.currentInstance.set(null); 149 if (tz != null) TimeZone.setDefault(tz); 150 } 151 } 152 153 /** 154 * Takes an input File containing the pack file, and generates a JarOutputStream. 155 * <p> 156 * Does not close its output. (The output can accumulate more elements.) 157 * @param in a File. 158 * @param out a JarOutputStream. 159 * @exception IOException if an error is encountered. 160 */ 161 public synchronized void unpack(File in, JarOutputStream out) throws IOException { 162 if (in == null) { 163 throw new NullPointerException("null input"); 164 } 165 if (out == null) { 166 throw new NullPointerException("null output"); 167 } 168 // Use the stream-based implementation. 169 // %%% Reconsider if native unpacker learns to memory-map the file. 170 try (FileInputStream instr = new FileInputStream(in)) { 171 unpack(instr, out); 172 } 173 if (props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) { 174 in.delete(); 175 } 176 } 177 178 private class DoUnpack { 179 final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); 180 181 { 182 props.setInteger(Pack200.Unpacker.PROGRESS, 0); 183 } 184 185 // Here's where the bits are read from disk: 186 final Package pkg = new Package(); 187 188 final boolean keepModtime 189 = Pack200.Packer.KEEP.equals( 190 props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP)); 191 final boolean keepDeflateHint 192 = Pack200.Packer.KEEP.equals( 193 props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP)); 194 final int modtime; 195 final boolean deflateHint; 196 { 197 if (!keepModtime) { 198 modtime = props.getTime(Utils.UNPACK_MODIFICATION_TIME); 199 } else { 200 modtime = pkg.default_modtime; 201 } 202 203 deflateHint = (keepDeflateHint) ? false : 204 props.getBoolean(java.util.jar.Pack200.Unpacker.DEFLATE_HINT); 205 } 206 207 // Checksum apparatus. 208 final CRC32 crc = new CRC32(); 209 final ByteArrayOutputStream bufOut = new ByteArrayOutputStream(); 210 final OutputStream crcOut = new CheckedOutputStream(bufOut, crc); 211 212 public void run(BufferedInputStream in, JarOutputStream out) throws IOException { 213 if (verbose > 0) { 214 props.list(System.out); 215 } 216 for (int seg = 1; ; seg++) { 217 unpackSegment(in, out); 218 219 // Try to get another segment. 220 if (!Utils.isPackMagic(Utils.readMagic(in))) break; 221 if (verbose > 0) 222 Utils.log.info("Finished segment #"+seg); 223 } 224 } 225 226 private void unpackSegment(InputStream in, JarOutputStream out) throws IOException { 227 props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"0"); 228 // Process the output directory or jar output. 229 new PackageReader(pkg, in).read(); 230 231 if (props.getBoolean("unpack.strip.debug")) pkg.stripAttributeKind("Debug"); 232 if (props.getBoolean("unpack.strip.compile")) pkg.stripAttributeKind("Compile"); 233 props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"50"); 234 pkg.ensureAllClassFiles(); 235 // Now write out the files. 236 Set<Package.Class> classesToWrite = new HashSet<>(pkg.getClasses()); 237 for (Package.File file : pkg.getFiles()) { 238 String name = file.nameString; 239 JarEntry je = new JarEntry(Utils.getJarEntryName(name)); 240 boolean deflate; 241 242 deflate = (keepDeflateHint) 243 ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) || 244 ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0)) 245 : deflateHint; 246 247 boolean needCRC = !deflate; // STORE mode requires CRC 248 249 if (needCRC) crc.reset(); 250 bufOut.reset(); 251 if (file.isClassStub()) { 252 Package.Class cls = file.getStubClass(); 253 assert(cls != null); 254 new ClassWriter(cls, needCRC ? crcOut : bufOut).write(); 255 classesToWrite.remove(cls); // for an error check 256 } else { 257 // collect data & maybe CRC 258 file.writeTo(needCRC ? crcOut : bufOut); 259 } 260 je.setMethod(deflate ? JarEntry.DEFLATED : JarEntry.STORED); 261 if (needCRC) { 262 if (verbose > 0) 263 Utils.log.info("stored size="+bufOut.size()+" and crc="+crc.getValue()); 264 265 je.setMethod(JarEntry.STORED); 266 je.setSize(bufOut.size()); 267 je.setCrc(crc.getValue()); 268 } 269 if (keepModtime) { 270 je.setTime(file.modtime); 271 // Convert back to milliseconds 272 je.setTime((long)file.modtime * 1000); 273 } else { 274 je.setTime((long)modtime * 1000); 275 } 276 out.putNextEntry(je); 277 bufOut.writeTo(out); 278 out.closeEntry(); 279 if (verbose > 0) 280 Utils.log.info("Writing "+Utils.zeString((ZipEntry)je)); 281 } 282 assert(classesToWrite.isEmpty()); 283 props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"100"); 284 pkg.reset(); // reset for the next segment, if any 285 } 286 } 287 }