View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.harmony.unpack200;
20  
21  import java.io.BufferedInputStream;
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.DataOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.PrintWriter;
30  import java.nio.charset.Charset;
31  import java.util.ArrayList;
32  import java.util.HashSet;
33  import java.util.List;
34  import java.util.Set;
35  import java.util.TimeZone;
36  import java.util.jar.JarEntry;
37  import java.util.jar.JarOutputStream;
38  import java.util.zip.CRC32;
39  import java.util.zip.GZIPInputStream;
40  import java.util.zip.ZipEntry;
41  
42  import org.apache.commons.compress.harmony.pack200.Codec;
43  import org.apache.commons.compress.harmony.pack200.Pack200Exception;
44  import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
45  import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
46  import org.apache.commons.compress.harmony.unpack200.bytecode.CPField;
47  import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethod;
48  import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
49  import org.apache.commons.compress.harmony.unpack200.bytecode.ClassConstantPool;
50  import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFile;
51  import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
52  import org.apache.commons.compress.harmony.unpack200.bytecode.InnerClassesAttribute;
53  import org.apache.commons.compress.harmony.unpack200.bytecode.SourceFileAttribute;
54  import org.apache.commons.io.IOUtils;
55  import org.apache.commons.io.input.BoundedInputStream;
56  
57  /**
58   * 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
59   * 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
60   * hard work in unpacking an archive falls to understanding a segment.
61   * <p>
62   * 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
63   * 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
64   * 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.
65   * </p>
66   * <p>
67   * 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
68   * 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}).
69   * 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.
70   * </p>
71   * <p>
72   * Format:
73   * </p>
74   * <pre>
75   *   pack200_archive:
76   *      (pack200_segment)+
77   *
78   *   pack200_segment:
79   *      segment_header
80   *      *band_headers :BYTE1
81   *      cp_bands
82   *      attr_definition_bands
83   *      ic_bands
84   *      class_bands
85   *      bc_bands
86   *      file_bands
87   * </pre>
88   */
89  public class Segment {
90  
91      /**
92       * Log level verbose {@value}
93       */
94      public static final int LOG_LEVEL_VERBOSE = 2;
95  
96      /**
97       * Log level standard {@value}
98       */
99      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 }