Segment.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.harmony.unpack200;

  18. import java.io.BufferedInputStream;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.DataOutputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.io.OutputStreamWriter;
  26. import java.io.PrintWriter;
  27. import java.nio.charset.Charset;
  28. import java.util.ArrayList;
  29. import java.util.HashSet;
  30. import java.util.List;
  31. import java.util.Set;
  32. import java.util.TimeZone;
  33. import java.util.jar.JarEntry;
  34. import java.util.jar.JarOutputStream;
  35. import java.util.zip.CRC32;
  36. import java.util.zip.GZIPInputStream;
  37. import java.util.zip.ZipEntry;

  38. import org.apache.commons.compress.harmony.pack200.Codec;
  39. import org.apache.commons.compress.harmony.pack200.Pack200Exception;
  40. import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
  41. import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
  42. import org.apache.commons.compress.harmony.unpack200.bytecode.CPField;
  43. import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethod;
  44. import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
  45. import org.apache.commons.compress.harmony.unpack200.bytecode.ClassConstantPool;
  46. import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFile;
  47. import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
  48. import org.apache.commons.compress.harmony.unpack200.bytecode.InnerClassesAttribute;
  49. import org.apache.commons.compress.harmony.unpack200.bytecode.SourceFileAttribute;
  50. import org.apache.commons.io.input.BoundedInputStream;

  51. /**
  52.  * 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
  53.  * 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
  54.  * hard work in unpacking an archive falls to understanding a segment.
  55.  *
  56.  * 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
  57.  * 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
  58.  * 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.
  59.  *
  60.  * 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
  61.  * 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}).
  62.  * 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.
  63.  */
  64. public class Segment {

  65.     public static final int LOG_LEVEL_VERBOSE = 2;

  66.     public static final int LOG_LEVEL_STANDARD = 1;

  67.     public static final int LOG_LEVEL_QUIET = 0;

  68.     private SegmentHeader header;

  69.     private CpBands cpBands;

  70.     private AttrDefinitionBands attrDefinitionBands;

  71.     private IcBands icBands;

  72.     private ClassBands classBands;

  73.     private BcBands bcBands;

  74.     private FileBands fileBands;

  75.     private boolean overrideDeflateHint;

  76.     private boolean deflateHint;

  77.     private boolean doPreRead;

  78.     private int logLevel;

  79.     private PrintWriter logStream;

  80.     private byte[][] classFilesContents;

  81.     private boolean[] fileDeflate;

  82.     private boolean[] fileIsClass;

  83.     private InputStream internalBuffer;

  84.     private ClassFile buildClassFile(final int classNum) {
  85.         final ClassFile classFile = new ClassFile();
  86.         final int[] major = classBands.getClassVersionMajor();
  87.         final int[] minor = classBands.getClassVersionMinor();
  88.         if (major != null) {
  89.             classFile.major = major[classNum];
  90.             classFile.minor = minor[classNum];
  91.         } else {
  92.             classFile.major = header.getDefaultClassMajorVersion();
  93.             classFile.minor = header.getDefaultClassMinorVersion();
  94.         }
  95.         // build constant pool
  96.         final ClassConstantPool cp = classFile.pool;
  97.         final int fullNameIndexInCpClass = classBands.getClassThisInts()[classNum];
  98.         final String fullName = cpBands.getCpClass()[fullNameIndexInCpClass];
  99.         // SourceFile attribute
  100.         int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then
  101.         // -1+1=0, so str.substring(0)
  102.         // == str

  103.         // Get the source file attribute
  104.         final List<Attribute> classAttributes = classBands.getClassAttributes()[classNum];
  105.         SourceFileAttribute sourceFileAttribute = null;
  106.         for (final Attribute classAttribute : classAttributes) {
  107.             if (classAttribute.isSourceFileAttribute()) {
  108.                 sourceFileAttribute = (SourceFileAttribute) classAttribute;
  109.             }
  110.         }

  111.         if (sourceFileAttribute == null) {
  112.             // If we don't have a source file attribute yet, we need
  113.             // to infer it from the class.
  114.             final AttributeLayout SOURCE_FILE = attrDefinitionBands.getAttributeDefinitionMap().getAttributeLayout(AttributeLayout.ATTRIBUTE_SOURCE_FILE,
  115.                     AttributeLayout.CONTEXT_CLASS);
  116.             if (SOURCE_FILE.matches(classBands.getRawClassFlags()[classNum])) {
  117.                 int firstDollar = -1;
  118.                 for (int index = 0; index < fullName.length(); index++) {
  119.                     if (fullName.charAt(index) <= '$') {
  120.                         firstDollar = index;
  121.                     }
  122.                 }
  123.                 String fileName;

  124.                 if (firstDollar > -1 && i <= firstDollar) {
  125.                     fileName = fullName.substring(i, firstDollar) + ".java";
  126.                 } else {
  127.                     fileName = fullName.substring(i) + ".java";
  128.                 }
  129.                 sourceFileAttribute = new SourceFileAttribute(cpBands.cpUTF8Value(fileName, false));
  130.                 classFile.attributes = new Attribute[] { (Attribute) cp.add(sourceFileAttribute) };
  131.             } else {
  132.                 classFile.attributes = new Attribute[] {};
  133.             }
  134.         } else {
  135.             classFile.attributes = new Attribute[] { (Attribute) cp.add(sourceFileAttribute) };
  136.         }

  137.         // If we see any class attributes, add them to the class's attributes
  138.         // that will
  139.         // be written out. Keep SourceFileAttributes out since we just
  140.         // did them above.
  141.         final List<Attribute> classAttributesWithoutSourceFileAttribute = new ArrayList<>(classAttributes.size());
  142.         for (int index = 0; index < classAttributes.size(); index++) {
  143.             final Attribute attrib = classAttributes.get(index);
  144.             if (!attrib.isSourceFileAttribute()) {
  145.                 classAttributesWithoutSourceFileAttribute.add(attrib);
  146.             }
  147.         }
  148.         final Attribute[] originalAttributes = classFile.attributes;
  149.         classFile.attributes = new Attribute[originalAttributes.length + classAttributesWithoutSourceFileAttribute.size()];
  150.         System.arraycopy(originalAttributes, 0, classFile.attributes, 0, originalAttributes.length);
  151.         for (int index = 0; index < classAttributesWithoutSourceFileAttribute.size(); index++) {
  152.             final Attribute attrib = classAttributesWithoutSourceFileAttribute.get(index);
  153.             cp.add(attrib);
  154.             classFile.attributes[originalAttributes.length + index] = attrib;
  155.         }

  156.         // this/superclass
  157.         final ClassFileEntry cfThis = cp.add(cpBands.cpClassValue(fullNameIndexInCpClass));
  158.         final ClassFileEntry cfSuper = cp.add(cpBands.cpClassValue(classBands.getClassSuperInts()[classNum]));
  159.         // add interfaces
  160.         final ClassFileEntry[] cfInterfaces = new ClassFileEntry[classBands.getClassInterfacesInts()[classNum].length];
  161.         for (i = 0; i < cfInterfaces.length; i++) {
  162.             cfInterfaces[i] = cp.add(cpBands.cpClassValue(classBands.getClassInterfacesInts()[classNum][i]));
  163.         }
  164.         // add fields
  165.         final ClassFileEntry[] cfFields = new ClassFileEntry[classBands.getClassFieldCount()[classNum]];
  166.         // fieldDescr and fieldFlags used to create this
  167.         for (i = 0; i < cfFields.length; i++) {
  168.             final int descriptorIndex = classBands.getFieldDescrInts()[classNum][i];
  169.             final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex];
  170.             final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex];
  171.             final CPUTF8 name = cpBands.cpUTF8Value(nameIndex);
  172.             final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex);
  173.             cfFields[i] = cp.add(new CPField(name, descriptor, classBands.getFieldFlags()[classNum][i], classBands.getFieldAttributes()[classNum][i]));
  174.         }
  175.         // add methods
  176.         final ClassFileEntry[] cfMethods = new ClassFileEntry[classBands.getClassMethodCount()[classNum]];
  177.         // methodDescr and methodFlags used to create this
  178.         for (i = 0; i < cfMethods.length; i++) {
  179.             final int descriptorIndex = classBands.getMethodDescrInts()[classNum][i];
  180.             final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex];
  181.             final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex];
  182.             final CPUTF8 name = cpBands.cpUTF8Value(nameIndex);
  183.             final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex);
  184.             cfMethods[i] = cp.add(new CPMethod(name, descriptor, classBands.getMethodFlags()[classNum][i], classBands.getMethodAttributes()[classNum][i]));
  185.         }
  186.         cp.addNestedEntries();

  187.         // add inner class attribute (if required)
  188.         boolean addInnerClassesAttr = false;
  189.         final IcTuple[] icLocal = getClassBands().getIcLocal()[classNum];
  190.         final boolean icLocalSent = icLocal != null;
  191.         final InnerClassesAttribute innerClassesAttribute = new InnerClassesAttribute("InnerClasses");
  192.         final IcTuple[] icRelevant = getIcBands().getRelevantIcTuples(fullName, cp);
  193.         final List<IcTuple> ic_stored = computeIcStored(icLocal, icRelevant);
  194.         for (final IcTuple icStored : ic_stored) {
  195.             final int innerClassIndex = icStored.thisClassIndex();
  196.             final int outerClassIndex = icStored.outerClassIndex();
  197.             final int simpleClassNameIndex = icStored.simpleClassNameIndex();

  198.             final String innerClassString = icStored.thisClassString();
  199.             final String outerClassString = icStored.outerClassString();
  200.             final String simpleClassName = icStored.simpleClassName();

  201.             CPUTF8 innerName = null;
  202.             CPClass outerClass = null;

  203.             final CPClass innerClass = innerClassIndex != -1 ? cpBands.cpClassValue(innerClassIndex) : cpBands.cpClassValue(innerClassString);
  204.             if (!icStored.isAnonymous()) {
  205.                 innerName = simpleClassNameIndex != -1 ? cpBands.cpUTF8Value(simpleClassNameIndex) : cpBands.cpUTF8Value(simpleClassName);
  206.             }

  207.             if (icStored.isMember()) {
  208.                 outerClass = outerClassIndex != -1 ? cpBands.cpClassValue(outerClassIndex) : cpBands.cpClassValue(outerClassString);
  209.             }
  210.             final int flags = icStored.F;
  211.             innerClassesAttribute.addInnerClassesEntry(innerClass, outerClass, innerName, flags);
  212.             addInnerClassesAttr = true;
  213.         }
  214.         // If ic_local is sent, and it's empty, don't add
  215.         // the inner classes attribute.
  216.         if (icLocalSent && icLocal.length == 0) {
  217.             addInnerClassesAttr = false;
  218.         }

  219.         // If ic_local is not sent and ic_relevant is empty,
  220.         // don't add the inner class attribute.
  221.         if (!icLocalSent && icRelevant.length == 0) {
  222.             addInnerClassesAttr = false;
  223.         }

  224.         if (addInnerClassesAttr) {
  225.             // Need to add the InnerClasses attribute to the
  226.             // existing classFile attributes.
  227.             final Attribute[] originalAttrs = classFile.attributes;
  228.             final Attribute[] newAttrs = new Attribute[originalAttrs.length + 1];
  229.             System.arraycopy(originalAttrs, 0, newAttrs, 0, originalAttrs.length);
  230.             newAttrs[newAttrs.length - 1] = innerClassesAttribute;
  231.             classFile.attributes = newAttrs;
  232.             cp.addWithNestedEntries(innerClassesAttribute);
  233.         }
  234.         // sort CP according to cp_All
  235.         cp.resolve(this);
  236.         // NOTE the indexOf is only valid after the cp.resolve()
  237.         // build up remainder of file
  238.         classFile.accessFlags = (int) classBands.getClassFlags()[classNum];
  239.         classFile.thisClass = cp.indexOf(cfThis);
  240.         classFile.superClass = cp.indexOf(cfSuper);
  241.         // TODO placate format of file for writing purposes
  242.         classFile.interfaces = new int[cfInterfaces.length];
  243.         for (i = 0; i < cfInterfaces.length; i++) {
  244.             classFile.interfaces[i] = cp.indexOf(cfInterfaces[i]);
  245.         }
  246.         classFile.fields = cfFields;
  247.         classFile.methods = cfMethods;
  248.         return classFile;
  249.     }

  250.     /**
  251.      * Given an ic_local and an ic_relevant, use them to calculate what should be added as ic_stored.
  252.      *
  253.      * @param icLocal    IcTuple[] array of local transmitted tuples
  254.      * @param icRelevant IcTuple[] array of relevant tuples
  255.      * @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
  256.      *         the case.
  257.      */
  258.     private List<IcTuple> computeIcStored(final IcTuple[] icLocal, final IcTuple[] icRelevant) {
  259.         final List<IcTuple> result = new ArrayList<>(icRelevant.length);
  260.         final List<IcTuple> duplicates = new ArrayList<>(icRelevant.length);
  261.         final Set<IcTuple> isInResult = new HashSet<>(icRelevant.length);

  262.         // need to compute:
  263.         // result = ic_local XOR ic_relevant

  264.         // add ic_local
  265.         if (icLocal != null) {
  266.             for (final IcTuple element : icLocal) {
  267.                 if (isInResult.add(element)) {
  268.                     result.add(element);
  269.                 }
  270.             }
  271.         }

  272.         // add ic_relevant
  273.         for (final IcTuple element : icRelevant) {
  274.             if (isInResult.add(element)) {
  275.                 result.add(element);
  276.             } else {
  277.                 duplicates.add(element);
  278.             }
  279.         }

  280.         // eliminate "duplicates"
  281.         duplicates.forEach(result::remove);

  282.         return result;
  283.     }

  284.     protected AttrDefinitionBands getAttrDefinitionBands() {
  285.         return attrDefinitionBands;
  286.     }

  287.     protected ClassBands getClassBands() {
  288.         return classBands;
  289.     }

  290.     public SegmentConstantPool getConstantPool() {
  291.         return cpBands.getConstantPool();
  292.     }

  293.     protected CpBands getCpBands() {
  294.         return cpBands;
  295.     }

  296.     protected IcBands getIcBands() {
  297.         return icBands;
  298.     }

  299.     public SegmentHeader getSegmentHeader() {
  300.         return header;
  301.     }

  302.     public void log(final int logLevel, final String message) {
  303.         if (this.logLevel >= logLevel) {
  304.             logStream.println(message);
  305.         }
  306.     }

  307.     /**
  308.      * Override the archive's deflate hint with the given boolean
  309.      *
  310.      * @param deflateHint the deflate hint to use
  311.      */
  312.     public void overrideDeflateHint(final boolean deflateHint) {
  313.         this.overrideDeflateHint = true;
  314.         this.deflateHint = deflateHint;
  315.     }

  316.     /**
  317.      * This performs the actual work of parsing against a non-static instance of Segment. This method is intended to run concurrently for multiple segments.
  318.      *
  319.      * @throws IOException      if a problem occurs during reading from the underlying stream
  320.      * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec
  321.      */
  322.     private void parseSegment() throws IOException, Pack200Exception {

  323.         header.unpack();
  324.         cpBands.unpack();
  325.         attrDefinitionBands.unpack();
  326.         icBands.unpack();
  327.         classBands.unpack();
  328.         bcBands.unpack();
  329.         fileBands.unpack();

  330.         int classNum = 0;
  331.         final int numberOfFiles = header.getNumberOfFiles();
  332.         final String[] fileName = fileBands.getFileName();
  333.         final int[] fileOptions = fileBands.getFileOptions();
  334.         final SegmentOptions options = header.getOptions();

  335.         classFilesContents = new byte[numberOfFiles][];
  336.         fileDeflate = new boolean[numberOfFiles];
  337.         fileIsClass = new boolean[numberOfFiles];

  338.         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
  339.         final DataOutputStream dos = new DataOutputStream(bos);

  340.         for (int i = 0; i < numberOfFiles; i++) {
  341.             String name = fileName[i];

  342.             final boolean nameIsEmpty = name == null || name.isEmpty();
  343.             final boolean isClass = (fileOptions[i] & 2) == 2 || nameIsEmpty;
  344.             if (isClass && nameIsEmpty) {
  345.                 name = cpBands.getCpClass()[classBands.getClassThisInts()[classNum]] + ".class";
  346.                 fileName[i] = name;
  347.             }

  348.             if (!overrideDeflateHint) {
  349.                 fileDeflate[i] = (fileOptions[i] & 1) == 1 || options.shouldDeflate();
  350.             } else {
  351.                 fileDeflate[i] = deflateHint;
  352.             }

  353.             fileIsClass[i] = isClass;

  354.             if (isClass) {
  355.                 final ClassFile classFile = buildClassFile(classNum);
  356.                 classFile.write(dos);
  357.                 dos.flush();

  358.                 classFilesContents[classNum] = bos.toByteArray();
  359.                 bos.reset();

  360.                 classNum++;
  361.             }
  362.         }
  363.     }

  364.     /**
  365.      * This performs reading the data from the stream into non-static instance of Segment. After the completion of this method stream can be freed.
  366.      *
  367.      * @param in the input stream to read from
  368.      * @throws IOException      if a problem occurs during reading from the underlying stream
  369.      * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec
  370.      */
  371.     private void readSegment(final InputStream in) throws IOException, Pack200Exception {
  372.         log(LOG_LEVEL_VERBOSE, "-------");
  373.         cpBands = new CpBands(this);
  374.         cpBands.read(in);
  375.         attrDefinitionBands = new AttrDefinitionBands(this);
  376.         attrDefinitionBands.read(in);
  377.         icBands = new IcBands(this);
  378.         icBands.read(in);
  379.         classBands = new ClassBands(this);
  380.         classBands.read(in);
  381.         bcBands = new BcBands(this);
  382.         bcBands.read(in);
  383.         fileBands = new FileBands(this);
  384.         fileBands.read(in);

  385.         fileBands.processFileBits();
  386.     }

  387.     public void setLogLevel(final int logLevel) {
  388.         this.logLevel = logLevel;
  389.     }

  390.     public void setLogStream(final OutputStream logStream) {
  391.         this.logStream = new PrintWriter(new OutputStreamWriter(logStream, Charset.defaultCharset()), false);
  392.     }

  393.     public void setPreRead(final boolean value) {
  394.         doPreRead = value;
  395.     }

  396.     /**
  397.      * Unpacks a packed stream (either .pack. or .pack.gz) into a corresponding JarOuputStream.
  398.      *
  399.      * @param inputStream  a packed input stream, preferably a {@link BoundedInputStream}.
  400.      * @param out output stream.
  401.      * @throws Pack200Exception if there is a problem unpacking
  402.      * @throws IOException      if there is a problem with I/O during unpacking
  403.      */
  404.     public void unpack(final InputStream inputStream, final JarOutputStream out) throws IOException, Pack200Exception {
  405.         unpackRead(inputStream);
  406.         unpackProcess();
  407.         unpackWrite(out);
  408.     }

  409.     void unpackProcess() throws IOException, Pack200Exception {
  410.         if (internalBuffer != null) {
  411.             readSegment(internalBuffer);
  412.         }
  413.         parseSegment();
  414.     }

  415.     /*
  416.      * Package-private accessors for unpacking stages
  417.      */
  418.     void unpackRead(final InputStream inputStream) throws IOException, Pack200Exception {
  419.         @SuppressWarnings("resource")
  420.         final InputStream in = Pack200UnpackerAdapter.newBoundedInputStream(inputStream);

  421.         header = new SegmentHeader(this);
  422.         header.read(in);

  423.         final int size = (int) header.getArchiveSize() - header.getArchiveSizeOffset();

  424.         if (doPreRead && header.getArchiveSize() != 0) {
  425.             final byte[] data = new byte[size];
  426.             in.read(data);
  427.             internalBuffer = new BufferedInputStream(new ByteArrayInputStream(data));
  428.         } else {
  429.             readSegment(in);
  430.         }
  431.     }

  432.     void unpackWrite(final JarOutputStream out) throws IOException {
  433.         writeJar(out);
  434.         if (logStream != null) {
  435.             logStream.close();
  436.         }
  437.     }

  438.     /**
  439.      * 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
  440.      * 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
  441.      * entries (e.g. further segments) to be written.
  442.      *
  443.      * @param out the JarOutputStream to write data to
  444.      * @throws IOException if an error occurs while reading or writing to the streams
  445.      */
  446.     public void writeJar(final JarOutputStream out) throws IOException {
  447.         final String[] fileName = fileBands.getFileName();
  448.         final int[] fileModtime = fileBands.getFileModtime();
  449.         final long[] fileSize = fileBands.getFileSize();
  450.         final byte[][] fileBits = fileBands.getFileBits();

  451.         // now write the files out
  452.         int classNum = 0;
  453.         final int numberOfFiles = header.getNumberOfFiles();
  454.         final long archiveModtime = header.getArchiveModtime();

  455.         for (int i = 0; i < numberOfFiles; i++) {
  456.             final String name = fileName[i];
  457.             // For Pack200 archives, modtime is in seconds
  458.             // from the epoch. JarEntries need it to be in
  459.             // milliseconds from the epoch.
  460.             // Even though we're adding two longs and multiplying
  461.             // by 1000, we won't overflow because both longs are
  462.             // always under 2^32.
  463.             final long modtime = 1000 * (archiveModtime + fileModtime[i]);
  464.             final boolean deflate = fileDeflate[i];

  465.             final JarEntry entry = new JarEntry(name);
  466.             if (deflate) {
  467.                 entry.setMethod(ZipEntry.DEFLATED);
  468.             } else {
  469.                 entry.setMethod(ZipEntry.STORED);
  470.                 final CRC32 crc = new CRC32();
  471.                 if (fileIsClass[i]) {
  472.                     crc.update(classFilesContents[classNum]);
  473.                     entry.setSize(classFilesContents[classNum].length);
  474.                 } else {
  475.                     crc.update(fileBits[i]);
  476.                     entry.setSize(fileSize[i]);
  477.                 }
  478.                 entry.setCrc(crc.getValue());
  479.             }
  480.             // On Windows at least, need to correct for timezone
  481.             entry.setTime(modtime - TimeZone.getDefault().getRawOffset());
  482.             out.putNextEntry(entry);

  483.             // write to output stream
  484.             if (fileIsClass[i]) {
  485.                 entry.setSize(classFilesContents[classNum].length);
  486.                 out.write(classFilesContents[classNum]);
  487.                 classNum++;
  488.             } else {
  489.                 entry.setSize(fileSize[i]);
  490.                 out.write(fileBits[i]);
  491.             }
  492.         }
  493.     }

  494. }