001: /*
002: * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.java.util.jar.pack;
027:
028: import java.util.*;
029: import java.util.jar.*;
030: import java.util.zip.*;
031: import java.io.*;
032: import java.beans.PropertyChangeListener;
033: import java.beans.PropertyChangeEvent;
034:
035: /*
036: * Implementation of the Pack provider.
037: * </pre></blockquote>
038: * @author John Rose
039: * @author Kumar Srinivasan
040: * @version 1.40, 05/05/07
041: */
042:
043: public class PackerImpl implements Pack200.Packer {
044:
045: /**
046: * Constructs a Packer object and sets the initial state of
047: * the packer engines.
048: */
049: public PackerImpl() {
050: _props = new PropMap();
051: //_props.getProperty() consults defaultProps invisibly.
052: //_props.putAll(defaultProps);
053: }
054:
055: // Private stuff.
056: final PropMap _props;
057:
058: /**
059: * Get the set of options for the pack and unpack engines.
060: * @return A sorted association of option key strings to option values.
061: */
062: public SortedMap properties() {
063: return _props;
064: }
065:
066: //Driver routines
067:
068: /**
069: * Takes a JarFile and converts into a pack-stream.
070: * <p>
071: * Closes its input but not its output. (Pack200 archives are appendable.)
072: * @param in a JarFile
073: * @param out an OutputStream
074: * @exception IOException if an error is encountered.
075: */
076: public void pack(JarFile in, OutputStream out) throws IOException {
077: assert (Utils.currentInstance.get() == null);
078: TimeZone tz = (_props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) ? null
079: : TimeZone.getDefault();
080: try {
081: Utils.currentInstance.set(this );
082: if (tz != null)
083: TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
084:
085: if ("0".equals(_props.getProperty(Pack200.Packer.EFFORT))) {
086: Utils.copyJarFile(in, out);
087: } else {
088: (new DoPack()).run(in, out);
089: in.close();
090: }
091: } finally {
092: Utils.currentInstance.set(null);
093: if (tz != null)
094: TimeZone.setDefault(tz);
095: }
096: }
097:
098: /**
099: * Takes a JarInputStream and converts into a pack-stream.
100: * <p>
101: * Closes its input but not its output. (Pack200 archives are appendable.)
102: * <p>
103: * The modification time and deflation hint attributes are not available,
104: * for the jar-manifest file and the directory containing the file.
105: *
106: * @see #MODIFICATION_TIME
107: * @see #DEFLATION_HINT
108: * @param in a JarInputStream
109: * @param out an OutputStream
110: * @exception IOException if an error is encountered.
111: */
112: public void pack(JarInputStream in, OutputStream out)
113: 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)
120: TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
121: if ("0".equals(_props.getProperty(Pack200.Packer.EFFORT))) {
122: Utils.copyJarFile(in, out);
123: } else {
124: (new DoPack()).run(in, out);
125: in.close();
126: }
127: } finally {
128: Utils.currentInstance.set(null);
129: if (tz != null)
130: TimeZone.setDefault(tz);
131:
132: }
133: }
134:
135: /**
136: * Register a listener for changes to options.
137: * @param listener An object to be invoked when a property is changed.
138: */
139: public void addPropertyChangeListener(
140: PropertyChangeListener listener) {
141: _props.addListener(listener);
142: }
143:
144: /**
145: * Remove a listener for the PropertyChange event.
146: * @param listener The PropertyChange listener to be removed.
147: */
148: public void removePropertyChangeListener(
149: PropertyChangeListener listener) {
150: _props.removeListener(listener);
151: }
152:
153: // All the worker bees.....
154:
155: // The packer worker.
156: private class DoPack {
157: final int verbose = _props.getInteger(Utils.DEBUG_VERBOSE);
158:
159: {
160: _props.setInteger(Pack200.Packer.PROGRESS, 0);
161: if (verbose > 0)
162: Utils.log.info(_props.toString());
163: }
164:
165: // Here's where the bits are collected before getting packed:
166: final Package pkg = new Package();
167:
168: final String unknownAttrCommand;
169: {
170: String uaMode = _props.getProperty(
171: Pack200.Packer.UNKNOWN_ATTRIBUTE,
172: Pack200.Packer.PASS);
173: if (!(Pack200.Packer.STRIP.equals(uaMode)
174: || Pack200.Packer.PASS.equals(uaMode) || Pack200.Packer.ERROR
175: .equals(uaMode))) {
176: throw new RuntimeException("Bad option: "
177: + Pack200.Packer.UNKNOWN_ATTRIBUTE + " = "
178: + uaMode);
179: }
180: unknownAttrCommand = uaMode.intern();
181: }
182:
183: final HashMap attrDefs;
184: final HashMap attrCommands;
185: {
186: HashMap attrDefs = new HashMap();
187: HashMap attrCommands = new HashMap();
188: String[] keys = { Pack200.Packer.CLASS_ATTRIBUTE_PFX,
189: Pack200.Packer.FIELD_ATTRIBUTE_PFX,
190: Pack200.Packer.METHOD_ATTRIBUTE_PFX,
191: Pack200.Packer.CODE_ATTRIBUTE_PFX };
192: int[] ctypes = { Constants.ATTR_CONTEXT_CLASS,
193: Constants.ATTR_CONTEXT_FIELD,
194: Constants.ATTR_CONTEXT_METHOD,
195: Constants.ATTR_CONTEXT_CODE };
196: for (int i = 0; i < ctypes.length; i++) {
197: String pfx = keys[i];
198: Map map = _props.prefixMap(pfx);
199: for (Iterator j = map.keySet().iterator(); j.hasNext();) {
200: String key = (String) j.next();
201: assert (key.startsWith(pfx));
202: String name = key.substring(pfx.length());
203: String layout = _props.getProperty(key);
204: Object lkey = Attribute.keyForLookup(ctypes[i],
205: name);
206: if (Pack200.Packer.STRIP.equals(layout)
207: || Pack200.Packer.PASS.equals(layout)
208: || Pack200.Packer.ERROR.equals(layout)) {
209: attrCommands.put(lkey, layout.intern());
210: } else {
211: Attribute.define(attrDefs, ctypes[i], name,
212: layout);
213: if (verbose > 1) {
214: Utils.log.fine("Added layout for "
215: + Constants.ATTR_CONTEXT_NAME[i]
216: + " attribute " + name + " = "
217: + layout);
218: }
219: assert (attrDefs.containsKey(lkey));
220: }
221: }
222: }
223: if (attrDefs.size() > 0)
224: this .attrDefs = attrDefs;
225: else
226: this .attrDefs = null;
227: if (attrCommands.size() > 0)
228: this .attrCommands = attrCommands;
229: else
230: this .attrCommands = null;
231: }
232:
233: final boolean keepFileOrder = _props
234: .getBoolean(Pack200.Packer.KEEP_FILE_ORDER);
235: final boolean keepClassOrder = _props
236: .getBoolean(Utils.PACK_KEEP_CLASS_ORDER);
237:
238: final boolean keepModtime = Pack200.Packer.KEEP.equals(_props
239: .getProperty(Pack200.Packer.MODIFICATION_TIME));
240: final boolean latestModtime = Pack200.Packer.LATEST
241: .equals(_props
242: .getProperty(Pack200.Packer.MODIFICATION_TIME));
243: final boolean keepDeflateHint = Pack200.Packer.KEEP
244: .equals(_props.getProperty(Pack200.Packer.DEFLATE_HINT));
245: {
246: if (!keepModtime && !latestModtime) {
247: int modtime = _props
248: .getTime(Pack200.Packer.MODIFICATION_TIME);
249: if (modtime != Constants.NO_MODTIME) {
250: pkg.default_modtime = modtime;
251: }
252: }
253: if (!keepDeflateHint) {
254: boolean deflate_hint = _props
255: .getBoolean(Pack200.Packer.DEFLATE_HINT);
256: if (deflate_hint) {
257: pkg.default_options |= Constants.AO_DEFLATE_HINT;
258: }
259: }
260: }
261:
262: long totalOutputSize = 0;
263: int segmentCount = 0;
264: long segmentTotalSize = 0;
265: long segmentSize = 0; // running counter
266: final long segmentLimit;
267: {
268: long limit;
269: if (_props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "")
270: .equals(""))
271: limit = -1;
272: else
273: limit = _props.getLong(Pack200.Packer.SEGMENT_LIMIT);
274: limit = Math.min(Integer.MAX_VALUE, limit);
275: limit = Math.max(-1, limit);
276: if (limit == -1)
277: limit = Long.MAX_VALUE;
278: segmentLimit = limit;
279: }
280:
281: final List passFiles; // parsed pack.pass.file options
282: {
283: // Which class files will be passed through?
284: passFiles = _props
285: .getProperties(Pack200.Packer.PASS_FILE_PFX);
286: for (ListIterator i = passFiles.listIterator(); i.hasNext();) {
287: String file = (String) i.next();
288: if (file == null) {
289: i.remove();
290: continue;
291: }
292: file = Utils.getJarEntryName(file); // normalize '\\' to '/'
293: if (file.endsWith("/"))
294: file = file.substring(0, file.length() - 1);
295: i.set(file);
296: }
297: if (verbose > 0)
298: Utils.log.info("passFiles = " + passFiles);
299: }
300:
301: {
302: // Fill in permitted range of major/minor version numbers.
303: int ver;
304: if ((ver = _props.getInteger(Utils.COM_PREFIX
305: + "min.class.majver")) != 0)
306: pkg.min_class_majver = (short) ver;
307: if ((ver = _props.getInteger(Utils.COM_PREFIX
308: + "min.class.minver")) != 0)
309: pkg.min_class_minver = (short) ver;
310: if ((ver = _props.getInteger(Utils.COM_PREFIX
311: + "max.class.majver")) != 0)
312: pkg.max_class_majver = (short) ver;
313: if ((ver = _props.getInteger(Utils.COM_PREFIX
314: + "max.class.minver")) != 0)
315: pkg.max_class_minver = (short) ver;
316: if ((ver = _props.getInteger(Utils.COM_PREFIX
317: + "package.minver")) != 0)
318: pkg.package_minver = (short) ver;
319: if ((ver = _props.getInteger(Utils.COM_PREFIX
320: + "package.majver")) != 0)
321: pkg.package_majver = (short) ver;
322: }
323:
324: {
325: // Hook for testing: Forces use of special archive modes.
326: int opt = _props.getInteger(Utils.COM_PREFIX
327: + "archive.options");
328: if (opt != 0)
329: pkg.default_options |= opt;
330: }
331:
332: // (Done collecting options from _props.)
333:
334: boolean isClassFile(String name) {
335: if (!name.endsWith(".class"))
336: return false;
337: for (String prefix = name;;) {
338: if (passFiles.contains(prefix))
339: return false;
340: int chop = prefix.lastIndexOf('/');
341: if (chop < 0)
342: break;
343: prefix = prefix.substring(0, chop);
344: }
345: return true;
346: }
347:
348: boolean isMetaInfFile(String name) {
349: return name.startsWith("/" + Utils.METAINF)
350: || name.startsWith(Utils.METAINF);
351: }
352:
353: // Get a new package, based on the old one.
354: private void makeNextPackage() {
355: pkg.reset();
356: }
357:
358: class InFile {
359: final String name;
360: final JarFile jf;
361: final JarEntry je;
362: final File f;
363: int modtime = Constants.NO_MODTIME;
364: int options;
365:
366: InFile(String name) {
367: this .name = Utils.getJarEntryName(name);
368: this .f = new File(name);
369: this .jf = null;
370: this .je = null;
371: int timeSecs = getModtime(f.lastModified());
372: if (keepModtime && timeSecs != Constants.NO_MODTIME) {
373: this .modtime = timeSecs;
374: } else if (latestModtime
375: && timeSecs > pkg.default_modtime) {
376: pkg.default_modtime = timeSecs;
377: }
378: }
379:
380: InFile(JarFile jf, JarEntry je) {
381: this .name = Utils.getJarEntryName(je.getName());
382: this .f = null;
383: this .jf = jf;
384: this .je = je;
385: int timeSecs = getModtime(je.getTime());
386: if (keepModtime && timeSecs != Constants.NO_MODTIME) {
387: this .modtime = timeSecs;
388: } else if (latestModtime
389: && timeSecs > pkg.default_modtime) {
390: pkg.default_modtime = timeSecs;
391: }
392: if (keepDeflateHint
393: && je.getMethod() == JarEntry.DEFLATED) {
394: options |= Constants.FO_DEFLATE_HINT;
395: }
396: }
397:
398: InFile(JarEntry je) {
399: this (null, je);
400: }
401:
402: long getInputLength() {
403: long len = (je != null) ? je.getSize() : f.length();
404: assert (len >= 0) : this + ".len=" + len;
405: // Bump size by pathname length and modtime/def-hint bytes.
406: return Math.max(0, len) + name.length() + 5;
407: }
408:
409: int getModtime(long timeMillis) {
410: // Convert milliseconds to seconds.
411: long seconds = (timeMillis + 500) / 1000;
412: if ((int) seconds == seconds) {
413: return (int) seconds;
414: } else {
415: Utils.log.warning("overflow in modtime for " + f);
416: return Constants.NO_MODTIME;
417: }
418: }
419:
420: void copyTo(Package.File file) {
421: if (modtime != Constants.NO_MODTIME)
422: file.modtime = modtime;
423: file.options |= options;
424: }
425:
426: InputStream getInputStream() throws IOException {
427: if (jf != null)
428: return jf.getInputStream(je);
429: else
430: return new FileInputStream(f);
431: }
432:
433: public String toString() {
434: return name;
435: }
436: }
437:
438: private int nread = 0; // used only if (verbose > 0)
439:
440: private void noteRead(InFile f) {
441: nread++;
442: if (verbose > 2)
443: Utils.log.fine("...read " + f.name);
444: if (verbose > 0 && (nread % 1000) == 0)
445: Utils.log.info("Have read " + nread + " files...");
446: }
447:
448: void run(JarInputStream in, OutputStream out)
449: throws IOException {
450: // First thing we do is get the manifest, as JIS does
451: // not provide the Manifest as an entry.
452: if (in.getManifest() != null) {
453: ByteArrayOutputStream tmp = new ByteArrayOutputStream();
454: in.getManifest().write(tmp);
455: InputStream tmpIn = new ByteArrayInputStream(tmp
456: .toByteArray());
457: pkg.addFile(readFile(JarFile.MANIFEST_NAME, tmpIn));
458: }
459: for (JarEntry je; (je = in.getNextJarEntry()) != null;) {
460: InFile inFile = new InFile(je);
461:
462: String name = inFile.name;
463: Package.File bits = readFile(name, in);
464: Package.File file = null;
465: // (5078608) : discount the resource files in META-INF
466: // from segment computation.
467: long inflen = (isMetaInfFile(name)) ? 0L : inFile
468: .getInputLength();
469:
470: if ((segmentSize += inflen) > segmentLimit) {
471: segmentSize -= inflen;
472: int nextCount = -1; // don't know; it's a stream
473: flushPartial(out, nextCount);
474: }
475: if (verbose > 1)
476: Utils.log.fine("Reading " + name);
477:
478: assert (je.isDirectory() == name.endsWith("/"));
479:
480: if (isClassFile(name)) {
481: file = readClass(name, bits.getInputStream());
482: }
483: if (file == null) {
484: file = bits;
485: pkg.addFile(file);
486: }
487: inFile.copyTo(file);
488: noteRead(inFile);
489: }
490: flushAll(out);
491: }
492:
493: void run(JarFile in, OutputStream out) throws IOException {
494: List inFiles = scanJar(in);
495:
496: if (verbose > 0)
497: Utils.log.info("Reading " + inFiles.size()
498: + " files...");
499:
500: int numDone = 0;
501: for (Iterator i = inFiles.iterator(); i.hasNext();) {
502: InFile inFile = (InFile) i.next();
503: String name = inFile.name;
504: // (5078608) : discount the resource files completely from segmenting
505: long inflen = (isMetaInfFile(name)) ? 0L : inFile
506: .getInputLength();
507: if ((segmentSize += inflen) > segmentLimit) {
508: segmentSize -= inflen;
509: // Estimate number of remaining segments:
510: float filesDone = numDone + 1;
511: float segsDone = segmentCount + 1;
512: float filesToDo = inFiles.size() - filesDone;
513: float segsToDo = filesToDo * (segsDone / filesDone);
514: if (verbose > 1)
515: Utils.log.fine("Estimated segments to do: "
516: + segsToDo);
517: flushPartial(out, (int) Math.ceil(segsToDo));
518: }
519: InputStream strm = inFile.getInputStream();
520: if (verbose > 1)
521: Utils.log.fine("Reading " + name);
522: Package.File file = null;
523: if (isClassFile(name)) {
524: file = readClass(name, strm);
525: if (file == null) {
526: strm.close();
527: strm = inFile.getInputStream();
528: }
529: }
530: if (file == null) {
531: file = readFile(name, strm);
532: pkg.addFile(file);
533: }
534: inFile.copyTo(file);
535: strm.close(); // tidy up
536: noteRead(inFile);
537: numDone += 1;
538: }
539: flushAll(out);
540: }
541:
542: Package.File readClass(String fname, InputStream in)
543: throws IOException {
544: Package.Class cls = pkg.new Class(fname);
545: in = new BufferedInputStream(in);
546: ClassReader reader = new ClassReader(cls, in);
547: reader.setAttrDefs(attrDefs);
548: reader.setAttrCommands(attrCommands);
549: reader.unknownAttrCommand = unknownAttrCommand;
550: try {
551: reader.read();
552: } catch (Attribute.FormatException ee) {
553: // He passed up the category to us in layout.
554: if (ee.layout.equals(Pack200.Packer.PASS)) {
555: Utils.log
556: .warning("Passing class file uncompressed due to unrecognized attribute: "
557: + fname);
558: Utils.log.info(ee.toString());
559: return null;
560: }
561: // Otherwise, it must be an error.
562: throw ee;
563: }
564: pkg.addClass(cls);
565: return cls.file;
566: }
567:
568: // Read raw data.
569: Package.File readFile(String fname, InputStream in)
570: throws IOException {
571:
572: Package.File file = pkg.new File(fname);
573: file.readFrom(in);
574: if (file.isDirectory() && file.getFileLength() != 0)
575: throw new IllegalArgumentException(
576: "Non-empty directory: " + file.getFileName());
577: return file;
578: }
579:
580: void flushPartial(OutputStream out, int nextCount)
581: throws IOException {
582: if (pkg.files.size() == 0 && pkg.classes.size() == 0) {
583: return; // do not flush an empty segment
584: }
585: flushPackage(out, Math.max(1, nextCount));
586: _props.setInteger(Pack200.Packer.PROGRESS, 25);
587: // In case there will be another segment:
588: makeNextPackage();
589: segmentCount += 1;
590: segmentTotalSize += segmentSize;
591: segmentSize = 0;
592: }
593:
594: void flushAll(OutputStream out) throws IOException {
595: _props.setInteger(Pack200.Packer.PROGRESS, 50);
596: flushPackage(out, 0);
597: out.flush();
598: _props.setInteger(Pack200.Packer.PROGRESS, 100);
599: segmentCount += 1;
600: segmentTotalSize += segmentSize;
601: segmentSize = 0;
602: if (verbose > 0 && segmentCount > 1) {
603: Utils.log.info("Transmitted " + segmentTotalSize
604: + " input bytes in " + segmentCount
605: + " segments totaling " + totalOutputSize
606: + " bytes");
607: }
608: }
609:
610: /** Write all information in the current package segment
611: * to the output stream.
612: */
613: void flushPackage(OutputStream out, int nextCount)
614: throws IOException {
615: int nfiles = pkg.files.size();
616: if (!keepFileOrder) {
617: // Keeping the order of classes costs about 1%
618: // Keeping the order of all files costs something more.
619: if (verbose > 1)
620: Utils.log.fine("Reordering files.");
621: boolean stripDirectories = true;
622: pkg.reorderFiles(keepClassOrder, stripDirectories);
623: } else {
624: // Package builder must have created a stub for each class.
625: assert (pkg.files.containsAll(pkg.getClassStubs()));
626: // Order of stubs in file list must agree with classes.
627: List res = pkg.files;
628: assert ((res = new ArrayList(pkg.files)).retainAll(pkg
629: .getClassStubs()) || true);
630: assert (res.equals(pkg.getClassStubs()));
631: }
632: pkg.trimStubs();
633:
634: // Do some stripping, maybe.
635: if (_props.getBoolean(Utils.COM_PREFIX + "strip.debug"))
636: pkg.stripAttributeKind("Debug");
637: if (_props.getBoolean(Utils.COM_PREFIX + "strip.compile"))
638: pkg.stripAttributeKind("Compile");
639: if (_props.getBoolean(Utils.COM_PREFIX + "strip.constants"))
640: pkg.stripAttributeKind("Constant");
641: if (_props
642: .getBoolean(Utils.COM_PREFIX + "strip.exceptions"))
643: pkg.stripAttributeKind("Exceptions");
644: if (_props.getBoolean(Utils.COM_PREFIX
645: + "strip.innerclasses"))
646: pkg.stripAttributeKind("InnerClasses");
647:
648: // Must choose an archive version; PackageWriter does not.
649: if (pkg.package_majver <= 0)
650: pkg.choosePackageVersion();
651:
652: PackageWriter pw = new PackageWriter(pkg, out);
653: pw.archiveNextCount = nextCount;
654: pw.write();
655: out.flush();
656: if (verbose > 0) {
657: long outSize = pw.archiveSize0 + pw.archiveSize1;
658: totalOutputSize += outSize;
659: long inSize = segmentSize;
660: Utils.log.info("Transmitted " + nfiles + " files of "
661: + inSize + " input bytes in a segment of "
662: + outSize + " bytes");
663: }
664: }
665:
666: List scanJar(JarFile jf) throws IOException {
667: // Collect jar entries, preserving order.
668: List inFiles = new ArrayList();
669: for (Enumeration e = jf.entries(); e.hasMoreElements();) {
670: JarEntry je = (JarEntry) e.nextElement();
671: InFile inFile = new InFile(jf, je);
672: assert (je.isDirectory() == inFile.name.endsWith("/"));
673: inFiles.add(inFile);
674: }
675: return inFiles;
676: }
677: }
678: }
|