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