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