    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.harmony.unpack200;
    020
    021import java.io.BufferedInputStream;
    022import java.io.ByteArrayInputStream;
    023import java.io.ByteArrayOutputStream;
    024import java.io.DataOutputStream;
    025import java.io.IOException;
    026import java.io.InputStream;
    027import java.io.OutputStream;
    028import java.io.OutputStreamWriter;
    029import java.io.PrintWriter;
    030import java.nio.charset.Charset;
    031import java.util.ArrayList;
    032import java.util.HashSet;
    033import java.util.List;
    034import java.util.Set;
    035import java.util.TimeZone;
    036import java.util.jar.JarEntry;
    037import java.util.jar.JarOutputStream;
    038import java.util.zip.CRC32;
    039import java.util.zip.GZIPInputStream;
    040import java.util.zip.ZipEntry;
    041
    042import org.apache.commons.compress.harmony.pack200.Codec;
    043import org.apache.commons.compress.harmony.pack200.Pack200Exception;
    044import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
    045import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
    046import org.apache.commons.compress.harmony.unpack200.bytecode.CPField;
    047import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethod;
    048import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
    049import org.apache.commons.compress.harmony.unpack200.bytecode.ClassConstantPool;
    050import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFile;
    051import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
    052import org.apache.commons.compress.harmony.unpack200.bytecode.InnerClassesAttribute;
    053import org.apache.commons.compress.harmony.unpack200.bytecode.SourceFileAttribute;
    054import org.apache.commons.io.IOUtils;
    055import org.apache.commons.io.input.BoundedInputStream;
    056
    057/**
    058 * A Pack200 archive consists of one or more segments. Each segment is stand-alone, in the sense that every segment has the magic number header; thus, every
    059 * segment is also a valid archive. However, it is possible to combine (non-GZipped) archives into a single large archive by concatenation alone. Thus, all the
    060 * hard work in unpacking an archive falls to understanding a segment.
    061 * <p>
    062 * The first component of a segment is the header; this contains (amongst other things) the expected counts of constant pool entries, which in turn defines how
    063 * many values need to be read from the stream. Because values are variable width (see {@link Codec}), it is not possible to calculate the start of the next
    064 * segment, although one of the header values does hint at the size of the segment if non-zero, which can be used for buffering purposes.
    065 * </p>
    066 * <p>
    067 * Note that this does not perform any buffering of the input stream; each value will be read on a byte-by-byte basis. It does not perform GZip decompression
    068 * automatically; both of these are expected to be done by the caller if the stream has the magic header for GZip streams ({@link GZIPInputStream#GZIP_MAGIC}).
    069 * In any case, if GZip decompression is being performed the input stream will be buffered at a higher level, and thus this can read on a byte-oriented basis.
    070 * </p>
    071 * <p>
    072 * Format:
    073 * </p>
    074 * <pre>
    075 *   pack200_archive:
    076 *      (pack200_segment)+
    077 *
    078 *   pack200_segment:
    079 *      segment_header
    080 *      *band_headers :BYTE1
    081 *      cp_bands
    082 *      attr_definition_bands
    083 *      ic_bands
    084 *      class_bands
    085 *      bc_bands
    086 *      file_bands
    087 * </pre>
    088 */
    089public class Segment {
    090
    091    /**
    092     * Log level verbose {@value}
    093     */
    094    public static final int LOG_LEVEL_VERBOSE = 2;
    095
    096    /**
    097     * Log level standard {@value}
    098     */
    099    public static final int LOG_LEVEL_STANDARD = 1;
    100
    101    /**
    102     * Log level quiet {@value}
    103     */
    104    public static final int LOG_LEVEL_QUIET = 0;
    105
    106    private SegmentHeader header;
    107
    108    private CpBands cpBands;
    109
    110    private AttrDefinitionBands attrDefinitionBands;
    111
    112    private IcBands icBands;
    113
    114    private ClassBands classBands;
    115
    116    private BcBands bcBands;
    117
    118    private FileBands fileBands;
    119
    120    private boolean overrideDeflateHint;
    121
    122    private boolean deflateHint;
    123
    124    private boolean doPreRead;
    125
    126    private int logLevel;
    127
    128    private PrintWriter logPrintWriter;
    129
    130    private byte[][] classFilesContents;
    131
    132    private boolean[] fileDeflate;
    133
    134    private boolean[] fileIsClass;
    135
    136    private InputStream internalBuffer;
    137
    138    private ClassFile buildClassFile(final int classNum) {
    139        final ClassFile classFile = new ClassFile();
    140        final int[] major = classBands.getClassVersionMajor();
    141        final int[] minor = classBands.getClassVersionMinor();
    142        if (major != null) {
    143            classFile.major = major[classNum];
    144            classFile.minor = minor[classNum];
    145        } else {
    146            classFile.major = header.getDefaultClassMajorVersion();
    147            classFile.minor = header.getDefaultClassMinorVersion();
    148        }
    149        // build constant pool
    150        final ClassConstantPool cp = classFile.pool;
    151        final int fullNameIndexInCpClass = classBands.getClassThisInts()[classNum];
    152        final String fullName = cpBands.getCpClass()[fullNameIndexInCpClass];
    153        // SourceFile attribute
    154        int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then
    155        // -1+1=0, so str.substring(0)
    156        // == str
    157
    158        // Get the source file attribute
    159        final List<Attribute> classAttributes = classBands.getClassAttributes()[classNum];
    160        SourceFileAttribute sourceFileAttribute = null;
    161        for (final Attribute classAttribute : classAttributes) {
    162            if (classAttribute.isSourceFileAttribute()) {
    163                sourceFileAttribute = (SourceFileAttribute) classAttribute;
    164            }
    165        }
    166
    167        if (sourceFileAttribute == null) {
    168            // If we don't have a source file attribute yet, we need
    169            // to infer it from the class.
    170            final AttributeLayout SOURCE_FILE = attrDefinitionBands.getAttributeDefinitionMap().getAttributeLayout(AttributeLayout.ATTRIBUTE_SOURCE_FILE,
    171                    AttributeLayout.CONTEXT_CLASS);
    172            if (SOURCE_FILE.matches(classBands.getRawClassFlags()[classNum])) {
    173                int firstDollar = -1;
    174                for (int index = 0; index < fullName.length(); index++) {
    175                    if (fullName.charAt(index) <= '$') {
    176                        firstDollar = index;
    177                    }
    178                }
    179                final String fileName;
    180                if (firstDollar > -1 && i <= firstDollar) {
    181                    fileName = fullName.substring(i, firstDollar) + ".java";
    182                } else {
    183                    fileName = fullName.substring(i) + ".java";
    184                }
    185                sourceFileAttribute = new SourceFileAttribute(cpBands.cpUTF8Value(fileName, false));
    186                classFile.attributes = new Attribute[] { (Attribute) cp.add(sourceFileAttribute) };
    187            } else {
    188                classFile.attributes = new Attribute[] {};
    189            }
    190        } else {
    191            classFile.attributes = new Attribute[] { (Attribute) cp.add(sourceFileAttribute) };
    192        }
    193
    194        // If we see any class attributes, add them to the class's attributes
    195        // that will
    196        // be written out. Keep SourceFileAttributes out since we just
    197        // did them above.
    198        final List<Attribute> classAttributesWithoutSourceFileAttribute = new ArrayList<>(classAttributes.size());
    199        for (int index = 0; index < classAttributes.size(); index++) {
    200            final Attribute attrib = classAttributes.get(index);
    201            if (!attrib.isSourceFileAttribute()) {
    202                classAttributesWithoutSourceFileAttribute.add(attrib);
    203            }
    204        }
    205        final Attribute[] originalAttributes = classFile.attributes;
    206        classFile.attributes = new Attribute[originalAttributes.length + classAttributesWithoutSourceFileAttribute.size()];
    207        System.arraycopy(originalAttributes, 0, classFile.attributes, 0, originalAttributes.length);
    208        for (int index = 0; index < classAttributesWithoutSourceFileAttribute.size(); index++) {
    209            final Attribute attrib = classAttributesWithoutSourceFileAttribute.get(index);
    210            cp.add(attrib);
    211            classFile.attributes[originalAttributes.length + index] = attrib;
    212        }
    213
    214        // this/superclass
    215        final ClassFileEntry cfThis = cp.add(cpBands.cpClassValue(fullNameIndexInCpClass));
    216        final ClassFileEntry cfSuper = cp.add(cpBands.cpClassValue(classBands.getClassSuperInts()[classNum]));
    217        // add interfaces
    218        final ClassFileEntry[] cfInterfaces = new ClassFileEntry[classBands.getClassInterfacesInts()[classNum].length];
    219        for (i = 0; i < cfInterfaces.length; i++) {
    220            cfInterfaces[i] = cp.add(cpBands.cpClassValue(classBands.getClassInterfacesInts()[classNum][i]));
    221        }
    222        // add fields
    223        final ClassFileEntry[] cfFields = new ClassFileEntry[classBands.getClassFieldCount()[classNum]];
    224        // fieldDescr and fieldFlags used to create this
    225        for (i = 0; i < cfFields.length; i++) {
    226            final int descriptorIndex = classBands.getFieldDescrInts()[classNum][i];
    227            final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex];
    228            final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex];
    229            final CPUTF8 name = cpBands.cpUTF8Value(nameIndex);
    230            final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex);
    231            cfFields[i] = cp.add(new CPField(name, descriptor, classBands.getFieldFlags()[classNum][i], classBands.getFieldAttributes()[classNum][i]));
    232        }
    233        // add methods
    234        final ClassFileEntry[] cfMethods = new ClassFileEntry[classBands.getClassMethodCount()[classNum]];
    235        // methodDescr and methodFlags used to create this
    236        for (i = 0; i < cfMethods.length; i++) {
    237            final int descriptorIndex = classBands.getMethodDescrInts()[classNum][i];
    238            final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex];
    239            final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex];
    240            final CPUTF8 name = cpBands.cpUTF8Value(nameIndex);
    241            final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex);
    242            cfMethods[i] = cp.add(new CPMethod(name, descriptor, classBands.getMethodFlags()[classNum][i], classBands.getMethodAttributes()[classNum][i]));
    243        }
    244        cp.addNestedEntries();
    245
    246        // add inner class attribute (if required)
    247        boolean addInnerClassesAttr = false;
    248        final IcTuple[] icLocal = getClassBands().getIcLocal()[classNum];
    249        final boolean icLocalSent = icLocal != null;
    250        final InnerClassesAttribute innerClassesAttribute = new InnerClassesAttribute("InnerClasses");
    251        final IcTuple[] icRelevant = getIcBands().getRelevantIcTuples(fullName, cp);
    252        final List<IcTuple> ic_stored = computeIcStored(icLocal, icRelevant);
    253        for (final IcTuple icStored : ic_stored) {
    254            final int innerClassIndex = icStored.thisClassIndex();
    255            final int outerClassIndex = icStored.outerClassIndex();
    256            final int simpleClassNameIndex = icStored.simpleClassNameIndex();
    257
    258            final String innerClassString = icStored.thisClassString();
    259            final String outerClassString = icStored.outerClassString();
    260            final String simpleClassName = icStored.simpleClassName();
    261
    262            CPUTF8 innerName = null;
    263            CPClass outerClass = null;
    264
    265            final CPClass innerClass = innerClassIndex != -1 ? cpBands.cpClassValue(innerClassIndex) : cpBands.cpClassValue(innerClassString);
    266            if (!icStored.isAnonymous()) {
    267                innerName = simpleClassNameIndex != -1 ? cpBands.cpUTF8Value(simpleClassNameIndex) : cpBands.cpUTF8Value(simpleClassName);
    268            }
    269
    270            if (icStored.isMember()) {
    271                outerClass = outerClassIndex != -1 ? cpBands.cpClassValue(outerClassIndex) : cpBands.cpClassValue(outerClassString);
    272            }
    273            final int flags = icStored.F;
    274            innerClassesAttribute.addInnerClassesEntry(innerClass, outerClass, innerName, flags);
    275            addInnerClassesAttr = true;
    276        }
    277        // If ic_local is sent, and it's empty, don't add
    278        // the inner classes attribute.
    279        if (icLocalSent && icLocal.length == 0) {
    280            addInnerClassesAttr = false;
    281        }
    282
    283        // If ic_local is not sent and ic_relevant is empty,
    284        // don't add the inner class attribute.
    285        if (!icLocalSent && icRelevant.length == 0) {
    286            addInnerClassesAttr = false;
    287        }
    288
    289        if (addInnerClassesAttr) {
    290            // Need to add the InnerClasses attribute to the
    291            // existing classFile attributes.
    292            final Attribute[] originalAttrs = classFile.attributes;
    293            final Attribute[] newAttrs = new Attribute[originalAttrs.length + 1];
    294            System.arraycopy(originalAttrs, 0, newAttrs, 0, originalAttrs.length);
    295            newAttrs[newAttrs.length - 1] = innerClassesAttribute;
    296            classFile.attributes = newAttrs;
    297            cp.addWithNestedEntries(innerClassesAttribute);
    298        }
    299        // sort CP according to cp_All
    300        cp.resolve(this);
    301        // NOTE the indexOf is only valid after the cp.resolve()
    302        // build up remainder of file
    303        classFile.accessFlags = (int) classBands.getClassFlags()[classNum];
    304        classFile.thisClass = cp.indexOf(cfThis);
    305        classFile.superClass = cp.indexOf(cfSuper);
    306        // TODO placate format of file for writing purposes
    307        classFile.interfaces = new int[cfInterfaces.length];
    308        for (i = 0; i < cfInterfaces.length; i++) {
    309            classFile.interfaces[i] = cp.indexOf(cfInterfaces[i]);
    310        }
    311        classFile.fields = cfFields;
    312        classFile.methods = cfMethods;
    313        return classFile;
    314    }
    315
    316    /**
    317     * Given an ic_local and an ic_relevant, use them to calculate what should be added as ic_stored.
    318     *
    319     * @param icLocal    IcTuple[] array of local transmitted tuples
    320     * @param icRelevant IcTuple[] array of relevant tuples
    321     * @return List of tuples to be stored. If ic_local is null or empty, the values returned may not be correct. The caller will have to determine if this is
    322     *         the case.
    323     */
    324    private List<IcTuple> computeIcStored(final IcTuple[] icLocal, final IcTuple[] icRelevant) {
    325        final List<IcTuple> result = new ArrayList<>(icRelevant.length);
    326        final List<IcTuple> duplicates = new ArrayList<>(icRelevant.length);
    327        final Set<IcTuple> isInResult = new HashSet<>(icRelevant.length);
    328
    329        // need to compute:
    330        // result = ic_local XOR ic_relevant
    331
    332        // add ic_local
    333        if (icLocal != null) {
    334            for (final IcTuple element : icLocal) {
    335                if (isInResult.add(element)) {
    336                    result.add(element);
    337                }
    338            }
    339        }
    340
    341        // add ic_relevant
    342        for (final IcTuple element : icRelevant) {
    343            if (isInResult.add(element)) {
    344                result.add(element);
    345            } else {
    346                duplicates.add(element);
    347            }
    348        }
    349
    350        // eliminate "duplicates"
    351        duplicates.forEach(result::remove);
    352
    353        return result;
    354    }
    355
    356    protected AttrDefinitionBands getAttrDefinitionBands() {
    357        return attrDefinitionBands;
    358    }
    359
    360    protected ClassBands getClassBands() {
    361        return classBands;
    362    }
    363
    364    /**
    365     * Gets the constant pool.
    366     *
    367     * @return the constant pool.
    368     */
    369    public SegmentConstantPool getConstantPool() {
    370        return cpBands.getConstantPool();
    371    }
    372
    373    /**
    374     * Gets the constant pool bands.
    375     *
    376     * @return the constant pool bands.
    377     */
    378    protected CpBands getCpBands() {
    379        return cpBands;
    380    }
    381
    382    /**
    383     * Gets the inner class bands.
    384     *
    385     * @return the inner class bands.
    386     */
    387    protected IcBands getIcBands() {
    388        return icBands;
    389    }
    390
    391    /**
    392     * Gets the segment header.
    393     *
    394     * @return the segment header.
    395     */
    396    public SegmentHeader getSegmentHeader() {
    397        return header;
    398    }
    399
    400    /**
    401     * Logs a message.
    402     *
    403     * @param messageLevel the message level.
    404     * @param message the message.
    405     */
    406    public void log(final int messageLevel, final String message) {
    407        if (logLevel >= messageLevel && logPrintWriter != null) {
    408            logPrintWriter.println(message);
    409        }
    410    }
    411
    412    /**
    413     * Overrides the archive's deflate hint with the given boolean
    414     *
    415     * @param deflateHint the deflate hint to use
    416     */
    417    public void overrideDeflateHint(final boolean deflateHint) {
    418        this.overrideDeflateHint = true;
    419        this.deflateHint = deflateHint;
    420    }
    421
    422    /**
    423     * Performs the actual work of parsing against a non-static instance of Segment. This method is intended to run concurrently for multiple segments.
    424     *
    425     * @throws IOException      if a problem occurs during reading from the underlying stream
    426     * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec
    427     */
    428    private void parseSegment() throws IOException, Pack200Exception {
    429
    430        header.unpack();
    431        cpBands.unpack();
    432        attrDefinitionBands.unpack();
    433        icBands.unpack();
    434        classBands.unpack();
    435        bcBands.unpack();
    436        fileBands.unpack();
    437
    438        int classNum = 0;
    439        final int numberOfFiles = header.getNumberOfFiles();
    440        final String[] fileName = fileBands.getFileName();
    441        final int[] fileOptions = fileBands.getFileOptions();
    442        final SegmentOptions options = header.getOptions();
    443
    444        classFilesContents = new byte[numberOfFiles][];
    445        fileDeflate = new boolean[numberOfFiles];
    446        fileIsClass = new boolean[numberOfFiles];
    447
    448        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
    449        final DataOutputStream dos = new DataOutputStream(bos);
    450
    451        for (int i = 0; i < numberOfFiles; i++) {
    452            String name = fileName[i];
    453
    454            final boolean nameIsEmpty = name == null || name.isEmpty();
    455            final boolean isClass = (fileOptions[i] & 2) == 2 || nameIsEmpty;
    456            if (isClass && nameIsEmpty) {
    457                name = cpBands.getCpClass()[classBands.getClassThisInts()[classNum]] + ".class";
    458                fileName[i] = name;
    459            }
    460
    461            if (!overrideDeflateHint) {
    462                fileDeflate[i] = (fileOptions[i] & 1) == 1 || options.shouldDeflate();
    463            } else {
    464                fileDeflate[i] = deflateHint;
    465            }
    466
    467            fileIsClass[i] = isClass;
    468
    469            if (isClass) {
    470                final ClassFile classFile = buildClassFile(classNum);
    471                classFile.write(dos);
    472                dos.flush();
    473
    474                classFilesContents[classNum] = bos.toByteArray();
    475                bos.reset();
    476
    477                classNum++;
    478            }
    479        }
    480    }
    481
    482    /**
    483     * This performs reading the data from the stream into non-static instance of Segment. After the completion of this method stream can be freed.
    484     *
    485     * @param in the input stream to read from
    486     * @throws IOException      if a problem occurs during reading from the underlying stream
    487     * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec
    488     */
    489    private void readSegment(final InputStream in) throws IOException, Pack200Exception {
    490        log(LOG_LEVEL_VERBOSE, "-------");
    491        cpBands = new CpBands(this);
    492        cpBands.read(in);
    493        attrDefinitionBands = new AttrDefinitionBands(this);
    494        attrDefinitionBands.read(in);
    495        icBands = new IcBands(this);
    496        icBands.read(in);
    497        classBands = new ClassBands(this);
    498        classBands.read(in);
    499        bcBands = new BcBands(this);
    500        bcBands.read(in);
    501        fileBands = new FileBands(this);
    502        fileBands.read(in);
    503        fileBands.processFileBits();
    504    }
    505
    506    /**
    507     * Sets the log level.
    508     *
    509     * @param logLevel the log level.
    510     */
    511    public void setLogLevel(final int logLevel) {
    512        this.logLevel = logLevel;
    513    }
    514
    515    /**
    516     * Sets the log output stream.
    517     *
    518     * @param logStream log output stream.
    519     */
    520    public void setLogStream(final OutputStream logStream) {
    521        this.logPrintWriter = logStream != null ? new PrintWriter(new OutputStreamWriter(logStream, Charset.defaultCharset()), false) : null;
    522    }
    523
    524    /**
    525     * Sets whether unpacking buffers its input.
    526     *
    527     * @param value whether unpacking buffers its input.
    528     */
    529    public void setPreRead(final boolean value) {
    530        doPreRead = value;
    531    }
    532
    533    /**
    534     * Unpacks a packed stream (either .pack. or .pack.gz) into a corresponding JarOuputStream.
    535     *
    536     * @param inputStream  a packed input stream, preferably a {@link BoundedInputStream}.
    537     * @param out output stream.
    538     * @throws Pack200Exception if there is a problem unpacking
    539     * @throws IOException      if there is a problem with I/O during unpacking
    540     */
    541    public void unpack(final InputStream inputStream, final JarOutputStream out) throws IOException, Pack200Exception {
    542        unpackRead(inputStream);
    543        unpackProcess();
    544        unpackWrite(out);
    545    }
    546
    547    void unpackProcess() throws IOException, Pack200Exception {
    548        if (internalBuffer != null) {
    549            readSegment(internalBuffer);
    550        }
    551        parseSegment();
    552    }
    553
    554    void unpackRead(final InputStream inputStream) throws IOException, Pack200Exception {
    555        @SuppressWarnings("resource")
    556        final InputStream in = Pack200UnpackerAdapter.newBoundedInputStream(inputStream);
    557
    558        header = new SegmentHeader(this);
    559        header.read(in);
    560
    561        final int size = (int) header.getArchiveSize() - header.getArchiveSizeOffset();
    562
    563        if (doPreRead && header.getArchiveSize() != 0) {
    564            final byte[] data = new byte[size];
    565            in.read(data);
    566            internalBuffer = new BufferedInputStream(new ByteArrayInputStream(data));
    567        } else {
    568            readSegment(in);
    569        }
    570    }
    571
    572    void unpackWrite(final JarOutputStream out) throws IOException {
    573        writeJar(out);
    574        IOUtils.close(logPrintWriter);
    575    }
    576
    577    /**
    578     * Writes the segment to an output stream. The output stream should be pre-buffered for efficiency. Also takes the same input stream for reading, since the
    579     * file bits may not be loaded and thus just copied from one stream to another. Doesn't close the output stream when finished, in case there are more
    580     * entries (for example further segments) to be written.
    581     *
    582     * @param out the JarOutputStream to write data to
    583     * @throws IOException if an error occurs while reading or writing to the streams
    584     */
    585    public void writeJar(final JarOutputStream out) throws IOException {
    586        final String[] fileName = fileBands.getFileName();
    587        final int[] fileModtime = fileBands.getFileModtime();
    588        final long[] fileSize = fileBands.getFileSize();
    589        final byte[][] fileBits = fileBands.getFileBits();
    590
    591        // now write the files out
    592        int classNum = 0;
    593        final int numberOfFiles = header.getNumberOfFiles();
    594        final long archiveModtime = header.getArchiveModtime();
    595
    596        for (int i = 0; i < numberOfFiles; i++) {
    597            final String name = fileName[i];
    598            // For Pack200 archives, modtime is in seconds
    599            // from the epoch. JarEntries need it to be in
    600            // milliseconds from the epoch.
    601            // Even though we're adding two longs and multiplying
    602            // by 1000, we won't overflow because both longs are
    603            // always under 2^32.
    604            final long modtime = 1000 * (archiveModtime + fileModtime[i]);
    605            final boolean deflate = fileDeflate[i];
    606
    607            final JarEntry entry = new JarEntry(name);
    608            if (deflate) {
    609                entry.setMethod(ZipEntry.DEFLATED);
    610            } else {
    611                entry.setMethod(ZipEntry.STORED);
    612                final CRC32 crc = new CRC32();
    613                if (fileIsClass[i]) {
    614                    crc.update(classFilesContents[classNum]);
    615                    entry.setSize(classFilesContents[classNum].length);
    616                } else {
    617                    crc.update(fileBits[i]);
    618                    entry.setSize(fileSize[i]);
    619                }
    620                entry.setCrc(crc.getValue());
    621            }
    622            // On Windows at least, need to correct for timezone
    623            entry.setTime(modtime - TimeZone.getDefault().getRawOffset());
    624            out.putNextEntry(entry);
    625
    626            // write to output stream
    627            if (fileIsClass[i]) {
    628                entry.setSize(classFilesContents[classNum].length);
    629                out.write(classFilesContents[classNum]);
    630                classNum++;
    631            } else {
    632                entry.setSize(fileSize[i]);
    633                out.write(fileBits[i]);
    634            }
    635        }
    636    }
    637
    638}