rev 50498 : 8199871: Deprecate pack200 and unpack200 tools
Reviewed-by:
1 /*
2 * Copyright (c) 2003, 2015, 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 com.sun.java.util.jar.pack.Attribute.Layout;
29 import java.io.BufferedInputStream;
30 import java.io.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.time.ZoneOffset;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.ListIterator;
43 import java.util.Map;
44 import java.util.SortedMap;
45 import java.util.jar.JarEntry;
46 import java.util.jar.JarFile;
47 import java.util.jar.JarInputStream;
48 import java.util.jar.Pack200;
49
50
51 /*
52 * Implementation of the Pack provider.
53 * </pre></blockquote>
54 * @author John Rose
55 * @author Kumar Srinivasan
56 */
57
58
59 public class PackerImpl extends TLGlobals implements Pack200.Packer {
60
61 /**
62 * Constructs a Packer object and sets the initial state of
63 * the packer engines.
64 */
65 public PackerImpl() {}
66
67 /**
68 * Get the set of options for the pack and unpack engines.
69 * @return A sorted association of option key strings to option values.
70 */
71 public SortedMap<String, String> properties() {
72 return props;
73 }
74
75 //Driver routines
76
77 /**
78 * Takes a JarFile and converts into a pack-stream.
79 * <p>
80 * Closes its input but not its output. (Pack200 archives are appendable.)
81 * @param in a JarFile
82 * @param out an OutputStream
83 * @exception IOException if an error is encountered.
84 */
85 public synchronized void pack(JarFile in, OutputStream out) throws IOException {
86 assert(Utils.currentInstance.get() == null);
87 try {
88 Utils.currentInstance.set(this);
89 if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) {
90 Utils.copyJarFile(in, out);
91 } else {
92 (new DoPack()).run(in, out);
93 }
94 } finally {
95 Utils.currentInstance.set(null);
96 in.close();
97 }
98 }
99
100 /**
101 * Takes a JarInputStream and converts into a pack-stream.
102 * <p>
103 * Closes its input but not its output. (Pack200 archives are appendable.)
104 * <p>
105 * The modification time and deflation hint attributes are not available,
106 * for the jar-manifest file and the directory containing the file.
107 *
108 * @see #MODIFICATION_TIME
109 * @see #DEFLATION_HINT
110 * @param in a JarInputStream
111 * @param out an OutputStream
112 * @exception IOException if an error is encountered.
113 */
114 public synchronized void pack(JarInputStream in, OutputStream out) throws IOException {
115 assert(Utils.currentInstance.get() == null);
116 try {
117 Utils.currentInstance.set(this);
118 if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) {
119 Utils.copyJarFile(in, out);
120 } else {
121 (new DoPack()).run(in, out);
122 }
123 } finally {
124 Utils.currentInstance.set(null);
125 in.close();
126 }
127 }
128
129 // All the worker bees.....
130 // The packer worker.
131 private class DoPack {
132 final int verbose = props.getInteger(Utils.DEBUG_VERBOSE);
133
134 {
135 props.setInteger(Pack200.Packer.PROGRESS, 0);
136 if (verbose > 0) Utils.log.info(props.toString());
137 }
138
139 // Here's where the bits are collected before getting packed, we also
140 // initialize the version numbers now.
141 final Package pkg = new Package(Package.Version.makeVersion(props, "min.class"),
142 Package.Version.makeVersion(props, "max.class"),
143 Package.Version.makeVersion(props, "package"));
144
145 final String unknownAttrCommand;
146 {
147 String uaMode = props.getProperty(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS);
148 if (!(Pack200.Packer.STRIP.equals(uaMode) ||
149 Pack200.Packer.PASS.equals(uaMode) ||
150 Pack200.Packer.ERROR.equals(uaMode))) {
151 throw new RuntimeException("Bad option: " + Pack200.Packer.UNKNOWN_ATTRIBUTE + " = " + uaMode);
152 }
153 unknownAttrCommand = uaMode.intern();
154 }
155 final String classFormatCommand;
156 {
157 String fmtMode = props.getProperty(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS);
158 if (!(Pack200.Packer.PASS.equals(fmtMode) ||
159 Pack200.Packer.ERROR.equals(fmtMode))) {
160 throw new RuntimeException("Bad option: " + Utils.CLASS_FORMAT_ERROR + " = " + fmtMode);
161 }
162 classFormatCommand = fmtMode.intern();
163 }
164
165 final Map<Attribute.Layout, Attribute> attrDefs;
166 final Map<Attribute.Layout, String> attrCommands;
167 {
168 Map<Attribute.Layout, Attribute> lattrDefs = new HashMap<>();
169 Map<Attribute.Layout, String> lattrCommands = new HashMap<>();
170 String[] keys = {
171 Pack200.Packer.CLASS_ATTRIBUTE_PFX,
172 Pack200.Packer.FIELD_ATTRIBUTE_PFX,
173 Pack200.Packer.METHOD_ATTRIBUTE_PFX,
174 Pack200.Packer.CODE_ATTRIBUTE_PFX
175 };
176 int[] ctypes = {
177 Constants.ATTR_CONTEXT_CLASS,
178 Constants.ATTR_CONTEXT_FIELD,
179 Constants.ATTR_CONTEXT_METHOD,
180 Constants.ATTR_CONTEXT_CODE
181 };
182 for (int i = 0; i < ctypes.length; i++) {
183 String pfx = keys[i];
184 Map<String, String> map = props.prefixMap(pfx);
185 for (String key : map.keySet()) {
186 assert(key.startsWith(pfx));
187 String name = key.substring(pfx.length());
188 String layout = props.getProperty(key);
189 Layout lkey = Attribute.keyForLookup(ctypes[i], name);
190 if (Pack200.Packer.STRIP.equals(layout) ||
191 Pack200.Packer.PASS.equals(layout) ||
192 Pack200.Packer.ERROR.equals(layout)) {
193 lattrCommands.put(lkey, layout.intern());
194 } else {
195 Attribute.define(lattrDefs, ctypes[i], name, layout);
196 if (verbose > 1) {
197 Utils.log.fine("Added layout for "+Constants.ATTR_CONTEXT_NAME[i]+" attribute "+name+" = "+layout);
198 }
199 assert(lattrDefs.containsKey(lkey));
200 }
201 }
202 }
203 this.attrDefs = (lattrDefs.isEmpty()) ? null : lattrDefs;
204 this.attrCommands = (lattrCommands.isEmpty()) ? null : lattrCommands;
205 }
206
207 final boolean keepFileOrder
208 = props.getBoolean(Pack200.Packer.KEEP_FILE_ORDER);
209 final boolean keepClassOrder
210 = props.getBoolean(Utils.PACK_KEEP_CLASS_ORDER);
211
212 final boolean keepModtime
213 = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME));
214 final boolean latestModtime
215 = Pack200.Packer.LATEST.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME));
216 final boolean keepDeflateHint
217 = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.DEFLATE_HINT));
218 {
219 if (!keepModtime && !latestModtime) {
220 int modtime = props.getTime(Pack200.Packer.MODIFICATION_TIME);
221 if (modtime != Constants.NO_MODTIME) {
222 pkg.default_modtime = modtime;
223 }
224 }
225 if (!keepDeflateHint) {
226 boolean deflate_hint = props.getBoolean(Pack200.Packer.DEFLATE_HINT);
227 if (deflate_hint) {
228 pkg.default_options |= Constants.AO_DEFLATE_HINT;
229 }
230 }
231 }
232
233 long totalOutputSize = 0;
234 int segmentCount = 0;
235 long segmentTotalSize = 0;
236 long segmentSize = 0; // running counter
237 final long segmentLimit;
238 {
239 long limit;
240 if (props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "").equals(""))
241 limit = -1;
242 else
243 limit = props.getLong(Pack200.Packer.SEGMENT_LIMIT);
244 limit = Math.min(Integer.MAX_VALUE, limit);
245 limit = Math.max(-1, limit);
246 if (limit == -1)
247 limit = Long.MAX_VALUE;
248 segmentLimit = limit;
249 }
250
251 final List<String> passFiles; // parsed pack.pass.file options
252 {
253 // Which class files will be passed through?
254 passFiles = props.getProperties(Pack200.Packer.PASS_FILE_PFX);
255 for (ListIterator<String> i = passFiles.listIterator(); i.hasNext(); ) {
256 String file = i.next();
257 if (file == null) { i.remove(); continue; }
258 file = Utils.getJarEntryName(file); // normalize '\\' to '/'
259 if (file.endsWith("/"))
260 file = file.substring(0, file.length()-1);
261 i.set(file);
262 }
263 if (verbose > 0) Utils.log.info("passFiles = " + passFiles);
264 }
265
266 {
267 // Hook for testing: Forces use of special archive modes.
268 int opt = props.getInteger(Utils.COM_PREFIX+"archive.options");
269 if (opt != 0)
270 pkg.default_options |= opt;
271 }
272
273 // (Done collecting options from props.)
274
275 // Get a new package, based on the old one.
276 private void makeNextPackage() {
277 pkg.reset();
278 }
279
280 final class InFile {
281 final String name;
282 final JarFile jf;
283 final JarEntry je;
284 final File f;
285 int modtime = Constants.NO_MODTIME;
286 int options;
287 InFile(String name) {
288 this.name = Utils.getJarEntryName(name);
289 this.f = new File(name);
290 this.jf = null;
291 this.je = null;
292 int timeSecs = getModtime(f.lastModified());
293 if (keepModtime && timeSecs != Constants.NO_MODTIME) {
294 this.modtime = timeSecs;
295 } else if (latestModtime && timeSecs > pkg.default_modtime) {
296 pkg.default_modtime = timeSecs;
297 }
298 }
299 InFile(JarFile jf, JarEntry je) {
300 this.name = Utils.getJarEntryName(je.getName());
301 this.f = null;
302 this.jf = jf;
303 this.je = je;
304 int timeSecs = (int) je.getTimeLocal()
305 .atOffset(ZoneOffset.UTC)
306 .toEpochSecond();
307 if (keepModtime && timeSecs != Constants.NO_MODTIME) {
308 this.modtime = timeSecs;
309 } else if (latestModtime && timeSecs > pkg.default_modtime) {
310 pkg.default_modtime = timeSecs;
311 }
312 if (keepDeflateHint && je.getMethod() == JarEntry.DEFLATED) {
313 options |= Constants.FO_DEFLATE_HINT;
314 }
315 }
316 InFile(JarEntry je) {
317 this(null, je);
318 }
319 boolean isClassFile() {
320 if (!name.endsWith(".class") || name.endsWith("module-info.class")) {
321 return false;
322 }
323 for (String prefix = name;;) {
324 if (passFiles.contains(prefix)) {
325 return false;
326 }
327 int chop = prefix.lastIndexOf('/');
328 if (chop < 0) {
329 break;
330 }
331 prefix = prefix.substring(0, chop);
332 }
333 return true;
334 }
335 boolean isMetaInfFile() {
336 return name.startsWith("/" + Utils.METAINF)
337 || name.startsWith(Utils.METAINF);
338 }
339 boolean mustProcess() {
340 return !isMetaInfFile() && isClassFile();
341 }
342 long getInputLength() {
343 long len = (je != null)? je.getSize(): f.length();
344 assert(len >= 0) : this+".len="+len;
345 // Bump size by pathname length and modtime/def-hint bytes.
346 return Math.max(0, len) + name.length() + 5;
347 }
348 int getModtime(long timeMillis) {
349 // Convert milliseconds to seconds.
350 long seconds = (timeMillis+500) / 1000;
351 if ((int)seconds == seconds) {
352 return (int)seconds;
353 } else {
354 Utils.log.warning("overflow in modtime for "+f);
355 return Constants.NO_MODTIME;
356 }
357 }
358 void copyTo(Package.File file) {
359 if (modtime != Constants.NO_MODTIME)
360 file.modtime = modtime;
361 file.options |= options;
362 }
363 InputStream getInputStream() throws IOException {
364 if (jf != null)
365 return jf.getInputStream(je);
366 else
367 return new FileInputStream(f);
368 }
369
370 public String toString() {
371 return name;
372 }
373 }
374
375 private int nread = 0; // used only if (verbose > 0)
376 private void noteRead(InFile f) {
377 nread++;
378 if (verbose > 2)
379 Utils.log.fine("...read "+f.name);
380 if (verbose > 0 && (nread % 1000) == 0)
381 Utils.log.info("Have read "+nread+" files...");
382 }
383
384 void run(JarInputStream in, OutputStream out) throws IOException {
385 // First thing we do is get the manifest, as JIS does
386 // not provide the Manifest as an entry.
387 if (in.getManifest() != null) {
388 ByteArrayOutputStream tmp = new ByteArrayOutputStream();
389 in.getManifest().write(tmp);
390 InputStream tmpIn = new ByteArrayInputStream(tmp.toByteArray());
391 pkg.addFile(readFile(JarFile.MANIFEST_NAME, tmpIn));
392 }
393 for (JarEntry je; (je = in.getNextJarEntry()) != null; ) {
394 InFile inFile = new InFile(je);
395
396 String name = inFile.name;
397 Package.File bits = readFile(name, in);
398 Package.File file = null;
399 // (5078608) : discount the resource files in META-INF
400 // from segment computation.
401 long inflen = (inFile.isMetaInfFile())
402 ? 0L
403 : inFile.getInputLength();
404
405 if ((segmentSize += inflen) > segmentLimit) {
406 segmentSize -= inflen;
407 int nextCount = -1; // don't know; it's a stream
408 flushPartial(out, nextCount);
409 }
410 if (verbose > 1) {
411 Utils.log.fine("Reading " + name);
412 }
413
414 assert(je.isDirectory() == name.endsWith("/"));
415
416 if (inFile.mustProcess()) {
417 file = readClass(name, bits.getInputStream());
418 }
419 if (file == null) {
420 file = bits;
421 pkg.addFile(file);
422 }
423 inFile.copyTo(file);
424 noteRead(inFile);
425 }
426 flushAll(out);
427 }
428
429 void run(JarFile in, OutputStream out) throws IOException {
430 List<InFile> inFiles = scanJar(in);
431
432 if (verbose > 0)
433 Utils.log.info("Reading " + inFiles.size() + " files...");
434
435 int numDone = 0;
436 for (InFile inFile : inFiles) {
437 String name = inFile.name;
438 // (5078608) : discount the resource files completely from segmenting
439 long inflen = (inFile.isMetaInfFile())
440 ? 0L
441 : inFile.getInputLength() ;
442 if ((segmentSize += inflen) > segmentLimit) {
443 segmentSize -= inflen;
444 // Estimate number of remaining segments:
445 float filesDone = numDone+1;
446 float segsDone = segmentCount+1;
447 float filesToDo = inFiles.size() - filesDone;
448 float segsToDo = filesToDo * (segsDone/filesDone);
449 if (verbose > 1)
450 Utils.log.fine("Estimated segments to do: "+segsToDo);
451 flushPartial(out, (int) Math.ceil(segsToDo));
452 }
453 InputStream strm = inFile.getInputStream();
454 if (verbose > 1)
455 Utils.log.fine("Reading " + name);
456 Package.File file = null;
457 if (inFile.mustProcess()) {
458 file = readClass(name, strm);
459 if (file == null) {
460 strm.close();
461 strm = inFile.getInputStream();
462 }
463 }
464 if (file == null) {
465 file = readFile(name, strm);
466 pkg.addFile(file);
467 }
468 inFile.copyTo(file);
469 strm.close(); // tidy up
470 noteRead(inFile);
471 numDone += 1;
472 }
473 flushAll(out);
474 }
475
476 Package.File readClass(String fname, InputStream in) throws IOException {
477 Package.Class cls = pkg.new Class(fname);
478 in = new BufferedInputStream(in);
479 ClassReader reader = new ClassReader(cls, in);
480 reader.setAttrDefs(attrDefs);
481 reader.setAttrCommands(attrCommands);
482 reader.unknownAttrCommand = unknownAttrCommand;
483 try {
484 reader.read();
485 } catch (IOException ioe) {
486 String message = "Passing class file uncompressed due to";
487 if (ioe instanceof Attribute.FormatException) {
488 Attribute.FormatException ee = (Attribute.FormatException) ioe;
489 // He passed up the category to us in layout.
490 if (ee.layout.equals(Pack200.Packer.PASS)) {
491 Utils.log.info(ee.toString());
492 Utils.log.warning(message + " unrecognized attribute: " +
493 fname);
494 return null;
495 }
496 } else if (ioe instanceof ClassReader.ClassFormatException) {
497 ClassReader.ClassFormatException ce = (ClassReader.ClassFormatException) ioe;
498 if (classFormatCommand.equals(Pack200.Packer.PASS)) {
499 Utils.log.info(ce.toString());
500 Utils.log.warning(message + " unknown class format: " +
501 fname);
502 return null;
503 }
504 }
505 // Otherwise, it must be an error.
506 throw ioe;
507 }
508 pkg.addClass(cls);
509 return cls.file;
510 }
511
512 // Read raw data.
513 Package.File readFile(String fname, InputStream in) throws IOException {
514
515 Package.File file = pkg.new File(fname);
516 file.readFrom(in);
517 if (file.isDirectory() && file.getFileLength() != 0)
518 throw new IllegalArgumentException("Non-empty directory: "+file.getFileName());
519 return file;
520 }
521
522 void flushPartial(OutputStream out, int nextCount) throws IOException {
523 if (pkg.files.isEmpty() && pkg.classes.isEmpty()) {
524 return; // do not flush an empty segment
525 }
526 flushPackage(out, Math.max(1, nextCount));
527 props.setInteger(Pack200.Packer.PROGRESS, 25);
528 // In case there will be another segment:
529 makeNextPackage();
530 segmentCount += 1;
531 segmentTotalSize += segmentSize;
532 segmentSize = 0;
533 }
534
535 void flushAll(OutputStream out) throws IOException {
536 props.setInteger(Pack200.Packer.PROGRESS, 50);
537 flushPackage(out, 0);
538 out.flush();
539 props.setInteger(Pack200.Packer.PROGRESS, 100);
540 segmentCount += 1;
541 segmentTotalSize += segmentSize;
542 segmentSize = 0;
543 if (verbose > 0 && segmentCount > 1) {
544 Utils.log.info("Transmitted "
545 +segmentTotalSize+" input bytes in "
546 +segmentCount+" segments totaling "
547 +totalOutputSize+" bytes");
548 }
549 }
550
551
552 /** Write all information in the current package segment
553 * to the output stream.
554 */
555 void flushPackage(OutputStream out, int nextCount) throws IOException {
556 int nfiles = pkg.files.size();
557 if (!keepFileOrder) {
558 // Keeping the order of classes costs about 1%
559 // Keeping the order of all files costs something more.
560 if (verbose > 1) Utils.log.fine("Reordering files.");
561 boolean stripDirectories = true;
562 pkg.reorderFiles(keepClassOrder, stripDirectories);
563 } else {
564 // Package builder must have created a stub for each class.
565 assert(pkg.files.containsAll(pkg.getClassStubs()));
566 // Order of stubs in file list must agree with classes.
567 List<Package.File> res = pkg.files;
568 assert((res = new ArrayList<>(pkg.files))
569 .retainAll(pkg.getClassStubs()) || true);
570 assert(res.equals(pkg.getClassStubs()));
571 }
572 pkg.trimStubs();
573
574 // Do some stripping, maybe.
575 if (props.getBoolean(Utils.COM_PREFIX+"strip.debug")) pkg.stripAttributeKind("Debug");
576 if (props.getBoolean(Utils.COM_PREFIX+"strip.compile")) pkg.stripAttributeKind("Compile");
577 if (props.getBoolean(Utils.COM_PREFIX+"strip.constants")) pkg.stripAttributeKind("Constant");
578 if (props.getBoolean(Utils.COM_PREFIX+"strip.exceptions")) pkg.stripAttributeKind("Exceptions");
579 if (props.getBoolean(Utils.COM_PREFIX+"strip.innerclasses")) pkg.stripAttributeKind("InnerClasses");
580
581 PackageWriter pw = new PackageWriter(pkg, out);
582 pw.archiveNextCount = nextCount;
583 pw.write();
584 out.flush();
585 if (verbose > 0) {
586 long outSize = pw.archiveSize0+pw.archiveSize1;
587 totalOutputSize += outSize;
588 long inSize = segmentSize;
589 Utils.log.info("Transmitted "
590 +nfiles+" files of "
591 +inSize+" input bytes in a segment of "
592 +outSize+" bytes");
593 }
594 }
595
596 List<InFile> scanJar(JarFile jf) throws IOException {
597 // Collect jar entries, preserving order.
598 List<InFile> inFiles = new ArrayList<>();
599 try {
600 for (JarEntry je : Collections.list(jf.entries())) {
601 InFile inFile = new InFile(jf, je);
602 assert(je.isDirectory() == inFile.name.endsWith("/"));
603 inFiles.add(inFile);
604 }
605 } catch (IllegalStateException ise) {
606 throw new IOException(ise.getLocalizedMessage(), ise);
607 }
608 return inFiles;
609 }
610 }
611 }
--- EOF ---