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}