Package org.apache.harmony.pack200

Source Code of org.apache.harmony.pack200.Segment$SegmentConstantPool

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

// NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5
// NOTE: Do not extract strings as messages; this code is still a
// work-in-progress
// NOTE: Also, don't get rid of 'else' statements for the hell of it ...
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.zip.GZIPInputStream;

import org.apache.harmony.pack200.bytecode.Attribute;
import org.apache.harmony.pack200.bytecode.CPClass;
import org.apache.harmony.pack200.bytecode.CPField;
import org.apache.harmony.pack200.bytecode.CPMethod;
import org.apache.harmony.pack200.bytecode.ClassConstantPool;
import org.apache.harmony.pack200.bytecode.ClassFile;
import org.apache.harmony.pack200.bytecode.ClassFileEntry;
import org.apache.harmony.pack200.bytecode.ConstantValueAttribute;
import org.apache.harmony.pack200.bytecode.ExceptionsAttribute;
import org.apache.harmony.pack200.bytecode.SourceFileAttribute;

/**
* A Pack200 archive consists of one (or more) segments. Each segment is
* standalone, in the sense that every segment has the magic number header;
* thus, every 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 hard work in unpacking an archive falls to understanding
* a segment.
*
* This class implements the Pack200 specification by an entry point ({@link #parse(InputStream)})
* which in turn delegates to a variety of other parse methods. Each parse
* method corresponds (roughly) to the name of the bands in the Pack200
* specification.
*
* 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 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 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.
*
* 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
* 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}).
* 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.
*
* @author Alex Blewitt
* @version $Revision: $
*/
public class Segment {
  public class SegmentConstantPool {
    public static final int ALL = 0;

    public static final int CP_DOUBLE = 7;

    // define in archive order

    public static final int CP_FLOAT = 4; // TODO Check this

    public static final int CP_INT = 3;

    public static final int CP_LONG = 6;

    public static final int CP_STRING = 5;

    public static final int SIGNATURE = 2; // TODO and more to come --

    public static final int UTF_8 = 1;

    public Object getValue(int cp, long value) throws Pack200Exception {
      int index = (int) value;
      if (index == -1) {
        return null;
      } else if (index < 0) {
        throw new Pack200Exception("Cannot have a negative range");
      } else if (cp == UTF_8) {
        return cpUTF8[index];
      } else if (cp == CP_STRING) {
        return cpString[index];
      } else if (cp == SIGNATURE) {
        return cpSignature[index];
      } else if (cp == CP_INT) {
        return new Integer(cpInt[index]);
      } else if (cp == CP_FLOAT) {
        return new Float(cpFloat[index]);
      } else if (cp == CP_DOUBLE) {
        return new Double(cpDouble[index]);
      } else if (cp == CP_LONG) {
        return new Long(cpLong[index]);
      } else {
        // etc
        throw new Error("Get value incomplete");
      }
    }
  }

  /**
   * The magic header for a Pack200 Segment is 0xCAFED00D. I wonder where they
   * get their inspiration from ...
   */
  private static final int[] magic = { 0xCA, 0xFE, 0xD0, 0x0D };

  /**
   * Decode a segment from the given input stream. This does not attempt to
   * re-assemble or export any class files, but it contains enough information
   * to be able to re-assemble class files by external callers.
   *
   * @param in
   *            the input stream to read from TODO At this point, this must be
   *            a non-GZipped input stream, but this decoding could be done in
   *            this method in the future (but perhaps more likely on an
   *            archive as a whole)
   * @return a segment parsed from the input stream
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  public static Segment parse(InputStream in) throws IOException,
      Pack200Exception {
    Segment segment = new Segment();
    // See if file is GZip compressed
    if (in.markSupported()) {
      in.mark(2);
      if (((in.read() & 0xFF) | (in.read() & 0xFF) << 8) == GZIPInputStream.GZIP_MAGIC) {
        in.reset();
        in = new GZIPInputStream(in);
      } else {
        in.reset();
      }

    }
    segment.parseSegment(in);
    return segment;
  }

  /**
   * Completely reads in a byte array, akin to the implementation in
   * {@link java.lang.DataInputStream}. TODO Refactor out into a separate
   * InputStream handling class
   *
   * @param in
   *            the input stream to read from
   * @param data
   *            the byte array to read into
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private static void readFully(InputStream in, byte[] data)
      throws IOException, Pack200Exception {
    int total = in.read(data);
    if (total == -1)
      throw new EOFException("Failed to read any data from input stream");
    while (total < data.length) {
      int delta = in.read(data, total, data.length - total);
      if (delta == -1)
        throw new EOFException(
            "Failed to read some data from input stream");
      total += delta;
    }
  }

  private int archiveMajor;

  private int archiveMinor;

  private long archiveModtime;

  private long archiveSize;

  private int attributeDefinitionCount;

  private int[] attributeDefinitionHeader;

  private String[] attributeDefinitionLayout;

  private AttributeLayoutMap attributeDefinitionMap;

  private String[] attributeDefinitionName;

  private InputStream bandHeadersInputStream;

  private int bandHeadersSize;

  private int classAttrCount;

  private int classCount;

  private int[] classFieldCount;

  private long[] classFlags;

  private String[][] classInterfaces;

  private int[] classMethodCount;

  private String[] classSuper;

  private String[] classThis;

  private String[] cpClass;

  private int cpClassCount;

  private String[] cpDescriptor;

  private int cpDescriptorCount;

  private double[] cpDouble;

  private int cpDoubleCount;

  private String[] cpFieldClass;

  private int cpFieldCount;

  private Object cpFieldDescriptor;

  private float[] cpFloat;

  private int cpFloatCount;

  private String[] cpIMethodClass;

  private int cpIMethodCount;

  private String[] cpIMethodDescriptor;

  private int[] cpInt;

  private int cpIntCount;

  private long[] cpLong;

  private int cpLongCount;

  private String[] cpMethodClass;

  private int cpMethodCount;

  private String[] cpMethodDescriptor;

  private String[] cpSignature;

  private int cpSignatureCount;

  private String[] cpString;

  private int cpStringCount;

  private String[] cpUTF8;

  private int cpUTF8Count;

  private int defaultClassMajorVersion;

  private int defaultClassMinorVersion;

  private int fieldAttrCount;

  private ArrayList[][] fieldAttributes;

  private String[][] fieldDescr;

  private long[][] fieldFlags;

  private byte[][] fileBits;

  private long[] fileModtime;

  private String[] fileName;

  private long[] fileOptions;

  private long[] fileSize;

  private int[] icFlags;

  private Object icName;

  private String[] icOuterClass;

  private String[] icThisClass;

  private int innerClassCount;

  private int methodAttrCount;

  private ArrayList[][] methodAttributes;

  private String[][] methodDescr;

  private ExceptionsAttribute[][] methodExceptions;

  private long[][] methodFlags;

  private int numberOfFiles;

  private SegmentOptions options;

  private final SegmentConstantPool pool = new SegmentConstantPool();

  private int segmentsRemaining;

  private ClassFile buildClassFile(int classNum) {
    ClassFile classFile = new ClassFile();
    classFile.major = defaultClassMajorVersion; // TODO If
    // classVersionMajor[] use
    // that instead
    classFile.minor = defaultClassMinorVersion; // TODO if
    // classVersionMinor[] use
    // that instead
    // build constant pool
    ClassConstantPool cp = classFile.pool;
    String fullName = classThis[classNum];
    // SourceFile attribute
    int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then
    // -1+1=0, so str.substring(0)
    // == str
    String fileName = fullName.substring(i) + ".java";
    classFile.attributes = new Attribute[] { (Attribute) cp
        .add(new SourceFileAttribute(fileName)) };
    // this/superclass
    ClassFileEntry cfThis = cp.add(new CPClass(fullName));
    ClassFileEntry cfSuper = cp.add(new CPClass(classSuper[classNum]));
    // add interfaces
    ClassFileEntry cfInterfaces[] = new ClassFileEntry[classInterfaces[classNum].length];
    for (i = 0; i < cfInterfaces.length; i++) {
      cfInterfaces[i] = cp.add(new CPClass(classInterfaces[classNum][i]));
    }
    // add fields
    ClassFileEntry cfFields[] = new ClassFileEntry[classFieldCount[classNum]];
    // fieldDescr and fieldFlags used to create this
    for (i = 0; i < cfFields.length; i++) {
      cfFields[i] = cp.add(new CPField(fieldDescr[classNum][i],
          fieldFlags[classNum][i], fieldAttributes[classNum][i]));
    }
    // add methods
    ClassFileEntry cfMethods[] = new ClassFileEntry[classMethodCount[classNum]];
    // fieldDescr and fieldFlags used to create this
    for (i = 0; i < cfMethods.length; i++) {
      cfMethods[i] = cp.add(new CPMethod(methodDescr[classNum][i],
          methodFlags[classNum][i], methodAttributes[classNum][i]));
    }
    // sort CP according to cp_All
    cp.resolve();
    // print out entries
    debug("Constant pool looks like:");
    for (i = 1; i <= cp.size(); i++) {
      debug(String.valueOf(i) + ":" + String.valueOf(cp.get(i)));
    }
    // NOTE the indexOf is only valid after the cp.resolve()
    // build up remainder of file
    classFile.accessFlags = (int) classFlags[classNum];
    classFile.thisClass = cp.indexOf(cfThis);
    classFile.superClass = cp.indexOf(cfSuper);
    // TODO placate format of file for writing purposes
    classFile.interfaces = new int[cfInterfaces.length];
    for (i = 0; i < cfInterfaces.length; i++) {
      classFile.interfaces[i] = cp.indexOf(cfInterfaces[i]);
    }
    classFile.fields = cfFields;
    classFile.methods = cfMethods;
    return classFile;
  }

  /**
   * This is a local debugging message to aid the developer in writing this
   * class. It will be removed before going into production. If the property
   * 'debug.pack200' is set, this will generate messages to stderr; otherwise,
   * it will be silent.
   *
   * @param message
   * @deprecated this should be removed from production code
   */
  private void debug(String message) {
    if (System.getProperty("debug.pack200") != null) {
      System.err.println(message);
    }
  }

  /**
   * Decode a band and return an array of <code>int[]</code> values
   *
   * @param name
   *            the name of the band (primarily for logging/debugging
   *            purposes)
   * @param in
   *            the InputStream to decode from
   * @param defaultCodec
   *            the default codec for this band
   * @param count
   *            the number of elements to read
   * @return an array of decoded <code>int[]</code> values
   * @throws IOException
   *             if there is a problem reading from the underlying input
   *             stream
   * @throws Pack200Exception
   *             if there is a problem decoding the value or that the value is
   *             invalid
   */
  private int[] decodeBandInt(String name, InputStream in,
      BHSDCodec defaultCodec, int count) throws IOException,
      Pack200Exception {
    // TODO Might be able to improve this directly.
    int[] result = new int[count];

    // TODO We need to muck around in the scenario where the first value
    // read indicates
    // an uber-codec
    long[] longResult = decodeBandLong(name, in, defaultCodec, count);
    for (int i = 0; i < count; i++) {
      result[i] = (int) longResult[i];
    }
    return result;
  }

  /**
   * Decode a band and return an array of <code>long[]</code> values
   *
   * @param name
   *            the name of the band (primarily for logging/debugging
   *            purposes)
   * @param in
   *            the InputStream to decode from
   * @param codec
   *            the default codec for this band
   * @param count
   *            the number of elements to read
   * @return an array of decoded <code>long[]</code> values
   * @throws IOException
   *             if there is a problem reading from the underlying input
   *             stream
   * @throws Pack200Exception
   *             if there is a problem decoding the value or that the value is
   *             invalid
   */
  private long[] decodeBandLong(String name, InputStream in, BHSDCodec codec,
      int count) throws IOException, Pack200Exception {
    long[] result = codec.decode(count, in);
    if (result.length > 0) {
      int first = (int) result[0];
      if (codec.isSigned() && first >= -256 && first <= -1) {
        // TODO Well, switch codecs then ...
        Codec weShouldHaveUsed = CodecEncoding.getCodec(-1 - first,
            getBandHeadersInputStream(), codec);
        throw new Error("Bugger. We should have switched codec to "
            + weShouldHaveUsed);
      } else if (!codec.isSigned() && first >= codec.getL()
          && first <= codec.getL() + 255) {
        Codec weShouldHaveUsed = CodecEncoding.getCodec(first
            - codec.getL(), getBandHeadersInputStream(), codec);
        // TODO Well, switch codecs then ...
        throw new Error("Bugger. We should have switched codec to "
            + weShouldHaveUsed);
      }
    }
    // TODO Remove debugging code
    debug("Parsed *" + name + " (" + result.length + ")");
    return result;
  }

  /**
   * Decode a scalar from the band file. A scalar is like a band, but does not
   * perform any band code switching.
   *
   * @param name
   *            the name of the scalar (primarily for logging/debugging
   *            purposes)
   * @param in
   *            the input stream to read from
   * @param codec
   *            the codec for this scalar
   * @return the decoded value
   * @throws IOException
   *             if there is a problem reading from the underlying input
   *             stream
   * @throws Pack200Exception
   *             if there is a problem decoding the value or that the value is
   *             invalid
   */
  private long decodeScalar(String name, InputStream in, BHSDCodec codec)
      throws IOException, Pack200Exception {
    debug("Parsed #" + name + " (1)");
    return codec.decode(in);
  }

  /**
   * Decode a number of scalars from the band file. A scalar is like a band,
   * but does not perform any band code switching.
   *
   * @param name
   *            the name of the scalar (primarily for logging/debugging
   *            purposes)
   * @param in
   *            the input stream to read from
   * @param codec
   *            the codec for this scalar
   * @return an array of decoded <code>long[]</code> values
   * @throws IOException
   *             if there is a problem reading from the underlying input
   *             stream
   * @throws Pack200Exception
   *             if there is a problem decoding the value or that the value is
   *             invalid
   */
  private long[] decodeScalar(String name, InputStream in, BHSDCodec codec,
      int n) throws IOException, Pack200Exception {
    // TODO Remove debugging code
    debug("Parsed #" + name + " (" + n + ")");
    return codec.decode(n, in);
  }

  public long getArchiveModtime() {
    return archiveModtime;
  }

  public long getArchiveSize() {
    return archiveSize;
  }

  /**
   * Obtain the band headers data as an input stream. If no band headers are
   * present, this will return an empty input stream to prevent any further
   * reads taking place.
   *
   * Note that as a stream, data consumed from this input stream can't be
   * re-used. Data is only read from this stream if the encoding is such that
   * additional information needs to be decoded from the stream itself.
   *
   * @return the band headers input stream
   */
  public InputStream getBandHeadersInputStream() {
    if (bandHeadersInputStream == null) {
      bandHeadersInputStream = new ByteArrayInputStream(new byte[0]);
    }
    return bandHeadersInputStream;

  }

  public SegmentConstantPool getConstantPool() {
    return pool;
  }

  public int getNumberOfFiles() {
    return numberOfFiles;
  }

  private SegmentOptions getOptions() {
    return options;
  }

  public int getSegmentsRemaining() {
    return segmentsRemaining;
  }

  private void parseArchiveFileCounts(InputStream in) throws IOException,
      Pack200Exception {
    if (getOptions().hasArchiveFileCounts()) {
      setArchiveSize(decodeScalar("archive_size_hi", in, Codec.UNSIGNED5) << 32
          | decodeScalar("archive_size_lo", in, Codec.UNSIGNED5));
      setSegmentsRemaining(decodeScalar("archive_next_count", in,
          Codec.UNSIGNED5));
      setArchiveModtime(decodeScalar("archive_modtime", in,
          Codec.UNSIGNED5));
      setNumberOfFiles(decodeScalar("file_count", in, Codec.UNSIGNED5));
    }
  }

  private void parseArchiveSpecialCounts(InputStream in) throws IOException,
      Pack200Exception {
    if (getOptions().hasSpecialFormats()) {
      setBandHeadersSize(decodeScalar("band_headers_size", in,
          Codec.UNSIGNED5));
      setAttributeDefinitionCount(decodeScalar("attr_definition_count",
          in, Codec.UNSIGNED5));
    }
  }

  /**
   * Reads {@link #attributeDefinitionCount} attribute definitions from the
   * stream, into {@link #attributeDefinitionHeader},
   * {@link #attributeDefinitionName} and {@link #attributeDefinitionLayout}.
   * This affects the codecs that are used to parse non-standard bands. TODO
   * Currently, these values if present cause a failure in the parsing.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseAttributeDefinition(InputStream in) throws IOException,
      Pack200Exception {
    attributeDefinitionHeader = decodeBandInt("attr_definition_headers",
        in, Codec.BYTE1, attributeDefinitionCount);
    attributeDefinitionName = parseReferences("attr_definition_name", in,
        Codec.UNSIGNED5, attributeDefinitionCount, cpUTF8);
    attributeDefinitionLayout = parseReferences("attr_definition_layout",
        in, Codec.UNSIGNED5, attributeDefinitionCount, cpUTF8);
    if (attributeDefinitionCount > 0)
      throw new Error("No idea what the adc is for yet");
    attributeDefinitionMap = new AttributeLayoutMap();
  }

  /**
   * @param in
   * @throws Pack200Exception
   * @throws IOException
   */
  private void parseAttributeMethodExceptions(InputStream in)
      throws Pack200Exception, IOException {
    // TODO Should refactor this stuff into the layout somehow
    AttributeLayout layout = attributeDefinitionMap.getAttributeLayout(
        "Exceptions", AttributeLayout.CONTEXT_METHOD);
    Codec codec = layout.getCodec();
    methodExceptions = new ExceptionsAttribute[classCount][];
    int[][] numExceptions = new int[classCount][];
    for (int i = 0; i < classCount; i++) {
      numExceptions[i] = new int[methodFlags[i].length];
      for (int j = 0; j < methodFlags[i].length; j++) {
        long flag = methodFlags[i][j];
        if (layout.matches(flag)) {
          numExceptions[i][j] = (int) codec.decode(in);
        }
      }
    }
    for (int i = 0; i < classCount; i++) {
      methodExceptions[i] = new ExceptionsAttribute[methodFlags[i].length];
      for (int j = 0; j < methodFlags[i].length; j++) {
        long flag = methodFlags[i][j];
        int n = numExceptions[i][j];
        CPClass[] exceptions = new CPClass[n];
        if (layout.matches(flag)) {
          for (int k = 0; k < n; k++) {
            long result = codec.decode(in);
            exceptions[k] = new CPClass(cpClass[(int) result]);
          }
        }
        methodExceptions[i][j] = new ExceptionsAttribute(exceptions);
        methodAttributes[i][j].add(methodExceptions[i][j]);
      }
    }
  }

  /**
   * @param in
   *
   */
  private void parseAttributeMethodSignature(InputStream in)
      throws Pack200Exception, IOException {
    parseAttributeUnknown(AttributeLayout.ATTRIBUTE_SIGNATURE,
        AttributeLayout.CONTEXT_METHOD, methodFlags);
  }

  /**
   * @param name
   * @param flags
   * @throws Pack200Exception
   */
  private void parseAttributeUnknown(String name, int context, long[][] flags)
      throws Pack200Exception {
    debug("Parsing unknown attributes for " + name);
    AttributeLayout layout = attributeDefinitionMap.getAttributeLayout(
        name, context);
    for (int i = 0; i < flags.length; i++) {
      for (int j = 0; j < flags[i].length; j++) {
        if (layout.matches(flags[i][j]))
          throw new Error("We've got data for " + name
              + " and we don't know what to do with it (yet)");
      }
    }
  }

  private void parseBcBands(InputStream in) {
    debug("Unimplemented bc_bands");
  }

  private void parseClassAttrBands(InputStream in) throws IOException,
      Pack200Exception {
    classFlags = parseFlags("class_flags", in, classCount, Codec.UNSIGNED5,
        options.hasClassFlagsHi());
    for (int i = 0; i < classCount; i++) {
      long flag = classFlags[i];
      if ((flag & (1 << 16)) != 0)
        classAttrCount++;
    }
    if (classAttrCount > 0)
      throw new Error(
          "There are attribute flags, and I don't know what to do with them");
    debug("unimplemented class_attr_count");
    debug("unimplemented class_attr_indexes");
    debug("unimplemented class_attr_calls");
    AttributeLayout layout = attributeDefinitionMap.getAttributeLayout(
        AttributeLayout.ATTRIBUTE_SOURCE_FILE,
        AttributeLayout.CONTEXT_CLASS);
    for (int i = 0; i < classCount; i++) {
      long flag = classFlags[i];
      if (layout.matches(flag)) {
        // we've got a value to read
        // TODO File this as a sourcefile attribute and don't generate
        // everything below
        long result = layout.getCodec().decode(in);
        Object value = layout.getValue(result, this);
        debug("Processed value " + value + " for SourceFile");
      }
    }
    debug("unimplemented class_EnclosingMethod_RC");
    debug("unimplemented class_EnclosingMethod_RDN");
    debug("unimplemented class_Signature_RS");
    parseMetadataBands("class");
    debug("unimplemented class_InnerClasses_N");
    debug("unimplemented class_InnerClasses_RC");
    debug("unimplemented class_InnerClasses_F");
    debug("unimplemented class_InnerClasses_outer_RCN");
    debug("unimplemented class_InnerClasses_inner_RCN");
    debug("unimplemented class_file_version_minor_H");
    debug("unimplemented class_file_version_major_H");
  }

  private void parseClassBands(InputStream in) throws IOException,
      Pack200Exception {
    classThis = parseReferences("class_this", in, Codec.DELTA5, classCount,
        cpClass);
    classSuper = parseReferences("class_super", in, Codec.DELTA5,
        classCount, cpClass);
    classInterfaces = new String[classCount][];
    int[] classInterfaceLengths = decodeBandInt("class_interface_count",
        in, Codec.DELTA5, classCount);
    // for (int i = 0; i < classCount; i++) {
    classInterfaces = parseReferences("class_interface", in, Codec.DELTA5,
        classCount, classInterfaceLengths, cpClass);
    // }
    classFieldCount = decodeBandInt("class_field_count", in, Codec.DELTA5,
        classCount);
    classMethodCount = decodeBandInt("class_method_count", in,
        Codec.DELTA5, classCount);
    parseFieldBands(in);
    parseMethodBands(in);
    parseClassAttrBands(in);
    parseCodeBands(in);
  }

  private void parseClassCounts(InputStream in) throws IOException,
      Pack200Exception {
    setInnerClassCount(decodeScalar("ic_count", in, Codec.UNSIGNED5));
    setDefaultClassMinorVersion(decodeScalar("default_class_minver", in,
        Codec.UNSIGNED5));
    setDefaultClassMajorVersion(decodeScalar("default_class_majver", in,
        Codec.UNSIGNED5));
    setClassCount(decodeScalar("class_count", in, Codec.UNSIGNED5));
  }

  private void parseCodeAttrBands(InputStream in) {
    debug("unimplemented code_flags");
    debug("unimplemented code_attr_count");
    debug("unimplemented code_attr_indexes");
    debug("unimplemented code_attr_calls");
    debug("unimplemented code_LineNumberTable_N");
    debug("unimplemented code_LineNumberTable_bci_P");
    debug("unimplemented code_LineNumberTable_line");
    String[] types = { "LocalVariableTable", "LocalVariableTypeTable" };
    for (int i = 0; i < types.length; i++) {
      String type = types[i];
      debug("unimplemented code_" + type + "_N");
      debug("unimplemented code_" + type + "_bci_P");
      debug("unimplemented code_" + type + "_span_O");
      debug("unimplemented code_" + type + "_name_RU");
      debug("unimplemented code_" + type + "_type_RS");
      debug("unimplemented code_" + type + "_slot");
    }
  }

  private void parseCodeBands(InputStream in) throws Pack200Exception {
    // look through each method
    int codeBands = 0;
    AttributeLayout layout = attributeDefinitionMap.getAttributeLayout(
        AttributeLayout.ATTRIBUTE_CODE, AttributeLayout.CONTEXT_METHOD);

    for (int i = 0; i < classCount; i++) {
      for (int j = 0; j < methodFlags[i].length; j++) {
        long flag = methodFlags[i][j];
        if (layout.matches(flag))
          codeBands++;
      }
    }
    if (codeBands > 0)
      throw new Error(
          "Can't handle non-abstract, non-native methods/initializers at the moment (found "
              + codeBands + " code bands)");
    debug("unimplemented code_headers");
    debug("unimplemented code_max_stack");
    debug("unimplemented code_max_na_locals");
    debug("unimplemented code_hander_count");
    debug("unimplemented code_hander_start_P");
    debug("unimplemented code_hander_end_PO");
    debug("unimplemented code_hander_catch_PO");
    debug("unimplemented code_hander_class_RC");
    parseCodeAttrBands(in);
  }

  /**
   * Parses the constant pool class names, using {@link #cpClassCount} to
   * populate {@link #cpClass} from {@link #cpUTF8}.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseCpClass(InputStream in) throws IOException,
      Pack200Exception {
    cpClass = parseReferences("cp_Class", in, Codec.UDELTA5, cpClassCount,
        cpUTF8);
  }

  private void parseCpCounts(InputStream in) throws IOException,
      Pack200Exception {
    setCPUtf8Count(decodeScalar("cp_Utf8_count", in, Codec.UNSIGNED5));
    if (getOptions().hasCPNumberCounts()) {
      setCPIntCount(decodeScalar("cp_Int_count", in, Codec.UNSIGNED5));
      setCPFloatCount(decodeScalar("cp_Float_count", in, Codec.UNSIGNED5));
      setCPLongCount(decodeScalar("cp_Long_count", in, Codec.UNSIGNED5));
      setCPDoubleCount(decodeScalar("cp_Double_count", in,
          Codec.UNSIGNED5));
    }
    setCPStringCount(decodeScalar("cp_String_count", in, Codec.UNSIGNED5));
    setCPClassCount(decodeScalar("cp_Class_count", in, Codec.UNSIGNED5));
    setCPSignatureCount(decodeScalar("cp_Signature_count", in,
        Codec.UNSIGNED5));
    setCPDescriptorCount(decodeScalar("cp_Descr_count", in, Codec.UNSIGNED5));
    setCPFieldCount(decodeScalar("cp_Field_count", in, Codec.UNSIGNED5));
    setCPMethodCount(decodeScalar("cp_Method_count", in, Codec.UNSIGNED5));
    setCPIMethodCount(decodeScalar("cp_Imethod_count", in, Codec.UNSIGNED5));
  }

  /**
   * Parses the constant pool descriptor definitions, using
   * {@link #cpDescriptorCount} to populate {@link #cpDescriptor}. For ease
   * of use, the cpDescriptor is stored as a string of the form <i>name:type</i>,
   * largely to make it easier for representing field and method descriptors
   * (e.g. <code>out:java.lang.PrintStream</code>) in a way that is
   * compatible with passing String arrays.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseCpDescriptor(InputStream in) throws IOException,
      Pack200Exception {
    String[] cpDescriptorNames = parseReferences("cp_Descr_name", in,
        Codec.DELTA5, cpDescriptorCount, cpUTF8);
    String[] cpDescriptorTypes = parseReferences("cp_Descr_type", in,
        Codec.UDELTA5, cpDescriptorCount, cpSignature);
    cpDescriptor = new String[cpDescriptorCount];
    for (int i = 0; i < cpDescriptorCount; i++) {
      cpDescriptor[i] = cpDescriptorNames[i] + ":" + cpDescriptorTypes[i]; //$NON-NLS-1$
    }
  }

  private void parseCpDouble(InputStream in) throws IOException,
      Pack200Exception {
    cpDouble = new double[cpDoubleCount];
    long[] hiBits = decodeBandLong("cp_Double_hi", in, Codec.UDELTA5,
        cpDoubleCount);
    long[] loBits = decodeBandLong("cp_Double_lo", in, Codec.DELTA5,
        cpDoubleCount);
    for (int i = 0; i < cpDoubleCount; i++) {
      cpDouble[i] = Double.longBitsToDouble(hiBits[i] << 32 | loBits[i]);
    }
  }

  /**
   * Parses the constant pool field definitions, using {@link #cpFieldCount}
   * to populate {@link #cpFieldClass} and {@link #cpFieldDescriptor}.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseCpField(InputStream in) throws IOException,
      Pack200Exception {
    cpFieldClass = parseReferences("cp_Field_class", in, Codec.DELTA5,
        cpFieldCount, cpClass);
    cpFieldDescriptor = parseReferences("cp_Field_desc", in, Codec.UDELTA5,
        cpFieldCount, cpDescriptor);
  }

  private void parseCpFloat(InputStream in) throws IOException,
      Pack200Exception {
    cpFloat = new float[cpFloatCount];
    int floatBits[] = decodeBandInt("cp_Float", in, Codec.UDELTA5,
        cpFloatCount);
    for (int i = 0; i < cpFloatCount; i++) {
      cpFloat[i] = Float.intBitsToFloat(floatBits[i]);
    }
  }

  /**
   * Parses the constant pool interface method definitions, using
   * {@link #cpIMethodCount} to populate {@link #cpIMethodClass} and
   * {@link #cpIMethodDescriptor}.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseCpIMethod(InputStream in) throws IOException,
      Pack200Exception {
    cpIMethodClass = parseReferences("cp_Imethod_class", in, Codec.DELTA5,
        cpIMethodCount, cpClass);
    cpIMethodDescriptor = parseReferences("cp_Imethod_desc", in,
        Codec.UDELTA5, cpIMethodCount, cpDescriptor);
  }

  private void parseCpInt(InputStream in) throws IOException,
      Pack200Exception {
    cpInt = new int[cpIntCount];
    long last = 0;
    for (int i = 0; i < cpIntCount; i++) {
      last = Codec.UDELTA5.decode(in, last);
      cpInt[i] = (int) last;
    }
  }

  private void parseCpLong(InputStream in) throws IOException,
      Pack200Exception {
    cpLong = parseFlags("cp_Long", in, cpLongCount, new int[] { 1 },
        Codec.UDELTA5, Codec.DELTA5)[0];
  }

  /**
   * Parses the constant pool method definitions, using {@link #cpMethodCount}
   * to populate {@link #cpMethodClass} and {@link #cpMethodDescriptor}.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseCpMethod(InputStream in) throws IOException,
      Pack200Exception {
    cpMethodClass = parseReferences("cp_Method_class", in, Codec.DELTA5,
        cpMethodCount, cpClass);
    cpMethodDescriptor = parseReferences("cp_Method_desc", in,
        Codec.UDELTA5, cpMethodCount, cpDescriptor);
  }

  /**
   * Parses the constant pool signature classes, using
   * {@link #cpSignatureCount} to populate {@link #cpSignature}. A signature
   * form is akin to the bytecode representation of a class; Z for boolean, I
   * for int, [ for array etc. However, although classes are started with L,
   * the classname does not follow the form; instead, there is a separate
   * array of classes. So an array corresponding to
   * <code>public static void main(String args[])</code> has a form of
   * <code>[L(V)</code> and a classes array of
   * <code>[java.lang.String]</code>. The {@link #cpSignature} is a string
   * representation identical to the bytecode equivalent
   * <code>[Ljava/lang/String;(V)</code> TODO Check that the form is as
   * above and update other types e.g. J
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseCpSignature(InputStream in) throws IOException,
      Pack200Exception {
    String[] cpSignatureForm = parseReferences("cp_Signature_form", in,
        Codec.DELTA5, cpSignatureCount, cpUTF8);
    cpSignature = new String[cpSignatureCount];
    long last = 0;
    for (int i = 0; i < cpSignatureCount; i++) {
      String form = cpSignatureForm[i];
      int len = form.length();
      StringBuffer signature = new StringBuffer(64);
      ArrayList list = new ArrayList();
      for (int j = 0; j < len; j++) {
        char c = form.charAt(j);
        signature.append(c);
        if (c == 'L') {
          int index = (int) (last = Codec.UDELTA5.decode(in, last));
          String className = cpClass[index];
          list.add(className);
          signature.append(className);
        }
      }
      cpSignature[i] = signature.toString();
    }
  }

  /**
   * Parses the constant pool strings, using {@link #cpStringCount} to
   * populate {@link #cpString} from indexes into {@link #cpUTF8}.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseCpString(InputStream in) throws IOException,
      Pack200Exception {
    cpString = new String[cpStringCount];
    long last = 0;
    for (int i = 0; i < cpStringCount; i++) {
      int index = (int) (last = Codec.UDELTA5.decode(in, last));
      cpString[i] = cpUTF8[index];
    }
  }

  private void parseCpUtf8(InputStream in) throws IOException,
      Pack200Exception {
    // TODO Update codec.decode -> decodeScalar
    cpUTF8 = new String[cpUTF8Count];
    cpUTF8[0] = ""; //$NON-NLS-1$
    int[] prefix = new int[cpUTF8Count];
    int[] suffix = new int[cpUTF8Count];
    if (cpUTF8Count > 0) {
      prefix[0] = 0;
      suffix[0] = 0;
      if (cpUTF8Count > 1)
        prefix[1] = 0;
    }
    long last = 0;
    for (int i = 2; i < cpUTF8Count; i++) {
      last = prefix[i] = (int) Codec.DELTA5.decode(in, last);
    }
    int chars = 0;
    int bigSuffix = 0;
    for (int i = 1; i < cpUTF8Count; i++) {
      last = suffix[i] = (int) Codec.UNSIGNED5.decode(in);
      if (last == 0) {
        bigSuffix++;
      } else {
        chars += last;
      }
    }
    char data[] = new char[chars];
    for (int i = 0; i < data.length; i++) {
      data[i] = (char) Codec.CHAR3.decode(in);
    }
    // read in the big suffix data
    char bigSuffixData[][] = new char[bigSuffix][];
    last = 0;
    for (int i = 0; i < bigSuffix; i++) {
      last = (int) Codec.DELTA5.decode(in, last);
      bigSuffixData[i] = new char[(int) last];
    }
    // initialize big suffix data
    for (int i = 0; i < bigSuffix; i++) {
      char[] singleBigSuffixData = bigSuffixData[i];
      last = 0;
      for (int j = 0; j < singleBigSuffixData.length; j++) {
        last = singleBigSuffixData[j] = (char) Codec.DELTA5.decode(in,
            last);
      }
    }
    // go through the strings
    chars = 0;
    bigSuffix = 0;
    for (int i = 1; i < cpUTF8Count; i++) {
      String lastString = cpUTF8[i - 1];
      if (suffix[i] == 0) {
        // The big suffix stuff hasn't been tested, and I'll be
        // surprised if it works first time w/o errors ...
        cpUTF8[i] = lastString.substring(0, prefix[i])
            + new String(bigSuffixData[bigSuffix++]);
      } else {
        cpUTF8[i] = lastString.substring(0, prefix[i])
            + new String(data, chars, suffix[i]);
        chars += suffix[i];
      }
    }
  }

  private void parseFieldBands(InputStream in) throws IOException,
      Pack200Exception {
    fieldDescr = parseReferences("field_descr", in, Codec.DELTA5,
        classCount, classFieldCount, cpDescriptor);
    fieldFlags = parseFlags("field_flags", in, classCount, classFieldCount,
        Codec.UNSIGNED5, options.hasFieldFlagsHi());
    for (int i = 0; i < classCount; i++) {
      for (int j = 0; j < fieldFlags[i].length; j++) {
        long flag = fieldFlags[i][j];
        if ((flag & (1 << 16)) != 0)
          fieldAttrCount++;
      }
    }
    if (fieldAttrCount > 0)
      throw new Error(
          "There are attribute flags, and I don't know what to do with them");
    debug("unimplemented field_attr_indexes");
    debug("unimplemented field_attr_calls");
    AttributeLayout layout = attributeDefinitionMap.getAttributeLayout(
        "ConstantValue", AttributeLayout.CONTEXT_FIELD);
    Codec codec = layout.getCodec();
    fieldAttributes = new ArrayList[classCount][];
    for (int i = 0; i < classCount; i++) {
      fieldAttributes[i] = new ArrayList[fieldFlags[i].length];
      for (int j = 0; j < fieldFlags[i].length; j++) {
        fieldAttributes[i][j] = new ArrayList();
        long flag = fieldFlags[i][j];
        if (layout.matches(flag)) {
          // we've got a value to read
          long result = codec.decode(in);
          String desc = fieldDescr[i][j];
          int colon = desc.indexOf(':');
          // String name = desc.substring(0, colon);
          String type = desc.substring(colon + 1);
          // TODO Got to get better at this ... in any case, it should
          // be e.g. KIB or KIH
          if (type.equals("B") || type.equals("H"))
            type = "I";
          Object value = layout.getValue(result, type, this);
          fieldAttributes[i][j]
              .add(new ConstantValueAttribute(value));
          debug("Processed value " + value + " for ConstantValue");
        }
      }
    }
    debug("unimplemented field_Signature_RS");
    parseMetadataBands("field");
  }

  /**
   * Parses the file band headers (not including the actual bits themselves).
   * At the end of this parse call, the input stream will be positioned at the
   * start of the file_bits themselves, and there will be Sum(file_size) bits
   * remaining in the stream with BYTE1 compression. A decent implementation
   * will probably just stream the bytes out to the reconstituted Jar rather
   * than caching them.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseFileBands(InputStream in) throws IOException,
      Pack200Exception {
    if (false && System.getProperty("debug.pack200") != null) {
      // TODO HACK
      fileSize = new long[numberOfFiles];
      fileModtime = new long[numberOfFiles];
      fileOptions = new long[numberOfFiles];
      fileName = new String[numberOfFiles];
      Arrays.fill(fileName, "");
      return;
    }
    long last;
    fileName = parseReferences("file_name", in, Codec.UNSIGNED5,
        numberOfFiles, cpUTF8);
    fileSize = new long[numberOfFiles];
    if (options.hasFileSizeHi()) {
      last = 0;
      for (int i = 0; i < numberOfFiles; i++) {
        fileSize[i] = (last = Codec.UNSIGNED5.decode(in, last)) << 32;
      }
    }
    last = 0;
    for (int i = 0; i < numberOfFiles; i++) {
      fileSize[i] |= (last = Codec.UNSIGNED5.decode(in, last));
    }
    fileModtime = new long[numberOfFiles];
    if (options.hasFileModtime()) {
      last = 0;
      for (int i = 0; i < numberOfFiles; i++) {
        fileModtime[i] |= (last = Codec.DELTA5.decode(in, last));
      }
    }
    fileOptions = new long[numberOfFiles];
    if (options.hasFileOptions()) {
      last = 0;
      for (int i = 0; i < numberOfFiles; i++) {
        fileOptions[i] |= (last = Codec.UNSIGNED5.decode(in, last));
      }
    }
  }

  private long[] parseFlags(String name, InputStream in, int count,
      Codec codec, boolean hasHi) throws IOException, Pack200Exception {
    return parseFlags(name, in, 1, new int[] { count }, (hasHi ? codec
        : null), codec)[0];
  }

  private long[][] parseFlags(String name, InputStream in, int count,
      int counts[], Codec codec, boolean hasHi) throws IOException,
      Pack200Exception {
    return parseFlags(name, in, count, counts, (hasHi ? codec : null),
        codec);
  }

  private long[][] parseFlags(String name, InputStream in, int count,
      int counts[], Codec hiCodec, Codec loCodec) throws IOException,
      Pack200Exception {
    // TODO Move away from decoding into a parseBand type structure
    if (count == 0) {
      return new long[][] { {} };
    }
    long[][] result = new long[count][];
    // TODO What happens when the decode here indicates a different
    // encoding?
    // TODO Move this to a decodeBandInt
    long last = 0;
    for (int j = 0; j < count; j++) {
      result[j] = new long[counts[j]];
      for (int i = 0; i < counts[j] && hiCodec != null; i++) {
        last = hiCodec.decode(in, last);
        result[j][i] = last << 32;
      }
    }
    last = 0;
    for (int j = 0; j < count; j++)
      for (int i = 0; i < counts[j]; i++) {
        last = loCodec.decode(in, last);
        result[j][i] = result[j][i] | last;
      }
    // TODO Remove debugging code
    debug("Parsed *" + name + " (" + result.length + ")");
    return result;
  }

  private void parseIcBands(InputStream in) throws IOException,
      Pack200Exception {
    icThisClass = parseReferences("ic_this_class", in, Codec.UDELTA5,
        innerClassCount, cpClass);
    icFlags = new int[innerClassCount];
    long last = 0;
    int outerClasses = 0;
    // ic_flags
    for (int i = 0; i < innerClassCount; i++) {
      icFlags[i] = (int) (last = Codec.UNSIGNED5.decode(in, last));
      if ((icFlags[i] & 1 << 16) != 0)
        outerClasses++;
    }
    icOuterClass = parseReferences("ic_outer_class", in, Codec.DELTA5,
        outerClasses, cpClass);
    icName = parseReferences("ic_name", in, Codec.DELTA5, outerClasses,
        cpUTF8);
  }

  private void parseMetadataBands(String unit) throws Pack200Exception {
    String[] RxA;
    if ("method".equals(unit)) {
      RxA = new String[] { "RVA", "RIA", "RVPA", "RIPA", "AD" };
    } else if ("field".equals(unit) || "class".equals(unit)) {
      RxA = new String[] { "RVA", "RIA" };
    } else {
      throw new Pack200Exception("Unknown type of metadata unit " + unit);
    }
    for (int i = 0; i < RxA.length; i++) {
      String rxa = RxA[i];
      if (rxa.indexOf("P") >= 0) {
        debug("unimplemented " + unit + "_" + rxa + "_param_NB");
      }
      if (!rxa.equals("AD")) {
        debug("unimplemented " + unit + "_" + rxa + "_anno_N");
        debug("unimplemented " + unit + "_" + rxa + "_type_RS");
        debug("unimplemented " + unit + "_" + rxa + "_pair_N");
        debug("unimplemented " + unit + "_" + rxa + "_name_RU");
      }
      debug("unimplemented " + unit + "_" + rxa + "_T");
      debug("unimplemented " + unit + "_" + rxa + "_caseI_KI");
      debug("unimplemented " + unit + "_" + rxa + "_caseD_KD");
      debug("unimplemented " + unit + "_" + rxa + "_caseF_KF");
      debug("unimplemented " + unit + "_" + rxa + "_caseJ_KJ");
      debug("unimplemented " + unit + "_" + rxa + "_casec_RS");
      debug("unimplemented " + unit + "_" + rxa + "_caseet_RS");
      debug("unimplemented " + unit + "_" + rxa + "_caseec_RU");
      debug("unimplemented " + unit + "_" + rxa + "_cases_RU");
      debug("unimplemented " + unit + "_" + rxa + "_casearray_N");
      debug("unimplemented " + unit + "_" + rxa + "_nesttype_RS");
      debug("unimplemented " + unit + "_" + rxa + "_nestpair_N");
      debug("unimplemented " + unit + "_" + rxa + "_nestname_RU");
    }
  }

  private void parseMethodBands(InputStream in) throws IOException,
      Pack200Exception {
    methodDescr = parseReferences("method_descr", in, Codec.MDELTA5,
        classCount, classMethodCount, cpDescriptor);
    methodFlags = parseFlags("method_flags", in, classCount,
        classMethodCount, Codec.UNSIGNED5, options.hasMethodFlagsHi());
    for (int i = 0; i < classCount; i++) {
      for (int j = 0; j < methodFlags[i].length; j++) {
        long flag = methodFlags[i][j];
        if ((flag & (1 << 16)) != 0)
          methodAttrCount++;
      }
    }
    if (methodAttrCount > 0)
      throw new Error(
          "There are method attribute flags, and I don't know what to do with them");
    debug("unimplemented method_attr_count");
    debug("unimplemented method_attr_indexes");
    debug("unimplemented method_attr_calls");
    // assign empty method attributes
    methodAttributes = new ArrayList[classCount][];
    for (int i = 0; i < classCount; i++) {
      methodAttributes[i] = new ArrayList[methodFlags[i].length];
      for (int j = 0; j < methodFlags[i].length; j++) {
        methodAttributes[i][j] = new ArrayList();
      }
    }
    parseAttributeMethodExceptions(in);
    parseAttributeMethodSignature(in);
    parseMetadataBands("method");
  }

  /**
   * Helper method to parse <i>count</i> references from <code>in</code>,
   * using <code>codec</code> to decode the values as indexes into
   * <code>reference</code> (which is populated prior to this call). An
   * exception is thrown if a decoded index falls outside the range
   * [0..reference.length-1]. Unlike the other parseReferences, this
   * post-processes the result into an array of results.
   *
   * @param name
   *            TODO
   * @param in
   *            the input stream to read from
   * @param codec
   *            the codec to use for decoding
   * @param count
   *            the number of references to decode
   * @param reference
   *            the array of values to use for the indexes; often
   *            {@link #cpUTF8}
   *
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private String[][] parseReferences(String name, InputStream in,
      BHSDCodec codec, int count, int counts[], String[] reference)
      throws IOException, Pack200Exception {
    if (count == 0) {
      return new String[][] { {} };
    }
    String[][] result = new String[count][];
    int sum = 0;
    for (int i = 0; i < count; i++) {
      result[i] = new String[counts[i]];
      sum += counts[i];
    }
    // TODO Merge the decode and parsing of a multiple structure into one
    String[] result1 = new String[sum];
    int[] decode = decodeBandInt(name, in, codec, sum);
    for (int i1 = 0; i1 < sum; i1++) {
      int index = decode[i1];
      if (index < 0 || index >= reference.length)
        throw new Pack200Exception(
            "Something has gone wrong during parsing references");
      result1[i1] = reference[index];
    }
    String[] refs = result1;
    // TODO Merge the decode and parsing of a multiple structure into one
    int pos = 0;
    for (int i = 0; i < count; i++) {
      int num = counts[i];
      result[i] = new String[num];
      System.arraycopy(refs, pos, result[i], 0, num);
      pos += num;
    }
    return result;
  }

  /**
   * Helper method to parse <i>count</i> references from <code>in</code>,
   * using <code>codec</code> to decode the values as indexes into
   * <code>reference</code> (which is populated prior to this call). An
   * exception is thrown if a decoded index falls outside the range
   * [0..reference.length-1].
   *
   * @param name
   *            TODO
   * @param in
   *            the input stream to read from
   * @param codec
   *            the codec to use for decoding
   * @param count
   *            the number of references to decode
   * @param reference
   *            the array of values to use for the indexes; often
   *            {@link #cpUTF8}
   *
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private String[] parseReferences(String name, InputStream in,
      BHSDCodec codec, int count, String[] reference) throws IOException,
      Pack200Exception {
    return parseReferences(name, in, codec, 1, new int[] { count },
        reference)[0];
  }

  /**
   * This performs the actual work of parsing against a non-static instance of
   * Segment.
   *
   * @param in
   *            the input stream to read from
   * @throws IOException
   *             if a problem occurs during reading from the underlying stream
   * @throws Pack200Exception
   *             if a problem occurs with an unexpected value or unsupported
   *             codec
   */
  private void parseSegment(InputStream in) throws IOException,
      Pack200Exception {
    debug("-------");
    parseSegmentHeader(in);
    if (bandHeadersSize > 0) {
      byte[] bandHeaders = new byte[(int) bandHeadersSize];
      readFully(in, bandHeaders);
      setBandHeadersData(bandHeaders);
    }
    parseCpUtf8(in);
    parseCpInt(in);
    parseCpFloat(in);
    parseCpLong(in);
    parseCpDouble(in);
    parseCpString(in);
    parseCpClass(in);
    parseCpSignature(in);
    parseCpDescriptor(in);
    parseCpField(in);
    parseCpMethod(in);
    parseCpIMethod(in);
    parseAttributeDefinition(in);
    parseIcBands(in);
    parseClassBands(in);
    parseBcBands(in);
    parseFileBands(in);
  }

  private void parseSegmentHeader(InputStream in) throws IOException,
      Pack200Exception, Error, Pack200Exception {
    long word[] = decodeScalar("archive_magic_word", in, Codec.BYTE1,
        magic.length);
    for (int m = 0; m < magic.length; m++)
      if (word[m] != magic[m])
        throw new Error("Bad header");
    setArchiveMinorVersion((int) decodeScalar("archive_minver", in,
        Codec.UNSIGNED5));
    setArchiveMajorVersion((int) decodeScalar("archive_majver", in,
        Codec.UNSIGNED5));
    setOptions(new SegmentOptions((int) decodeScalar("archive_options", in,
        Codec.UNSIGNED5)));
    parseArchiveFileCounts(in);
    parseArchiveSpecialCounts(in);
    parseCpCounts(in);
    parseClassCounts(in);
  }

  private void processFileBits(InputStream in) throws IOException,
      Pack200Exception {
    // now read in the bytes
    fileBits = new byte[numberOfFiles][];
    for (int i = 0; i < numberOfFiles; i++) {
      int size = (int) fileSize[i];
      // TODO This buggers up if file_size > 2^32. Probably an array is
      // not the right choice, and
      // we should just serialize the bugger here?
      fileBits[i] = new byte[size];
      for (int j = 0; j < size; j++) {
        fileBits[i][j] = (byte) Codec.BYTE1.decode(in);
      }
    }
  }

  /**
   * Sets the major version of this archive.
   *
   * @param version
   *            the minor version of the archive
   * @throws Pack200Exception
   *             if the major version is not 150
   */
  private void setArchiveMajorVersion(int version) throws Pack200Exception {
    if (version != 150)
      throw new Pack200Exception("Invalid segment major version");
    archiveMajor = version;
  }

  /**
   * Sets the minor version of this archive
   *
   * @param version
   *            the minor version of the archive
   * @throws Pack200Exception
   *             if the minor version is not 7
   */
  private void setArchiveMinorVersion(int version) throws Pack200Exception {
    if (version != 7)
      throw new Pack200Exception("Invalid segment minor version");
    archiveMinor = version;
  }

  public void setArchiveModtime(long archiveModtime) {
    this.archiveModtime = archiveModtime;
  }

  public void setArchiveSize(long archiveSize) {
    this.archiveSize = archiveSize;
  }

  private void setAttributeDefinitionCount(long valuie) {
    this.attributeDefinitionCount = (int) valuie;
  }

  private void setBandHeadersData(byte[] bandHeaders) {
    this.bandHeadersInputStream = new ByteArrayInputStream(bandHeaders);
  }

  private void setBandHeadersSize(long value) {
    this.bandHeadersSize = (int) value;
  }

  private void setClassCount(long value) {
    classCount = (int) value;
  }

  private void setCPClassCount(long value) {
    cpClassCount = (int) value;
  }

  private void setCPDescriptorCount(long value) {
    cpDescriptorCount = (int) value;
  }

  private void setCPDoubleCount(long value) {
    cpDoubleCount = (int) value;
  }

  private void setCPFieldCount(long value) {
    cpFieldCount = (int) value;
  }

  private void setCPFloatCount(long value) {
    cpFloatCount = (int) value;
  }

  private void setCPIMethodCount(long value) {
    cpIMethodCount = (int) value;
  }

  private void setCPIntCount(long value) {
    cpIntCount = (int) value;
  }

  private void setCPLongCount(long value) {
    cpLongCount = (int) value;
  }

  private void setCPMethodCount(long value) {
    cpMethodCount = (int) value;
  }

  private void setCPSignatureCount(long value) {
    cpSignatureCount = (int) value;
  }

  private void setCPStringCount(long value) {
    cpStringCount = (int) value;
  }

  private void setCPUtf8Count(long value) {
    cpUTF8Count = (int) value;
  }

  private void setDefaultClassMajorVersion(long value) {
    defaultClassMajorVersion = (int) value;
  }

  private void setDefaultClassMinorVersion(long value) {
    defaultClassMinorVersion = (int) value;
  }

  private void setInnerClassCount(long value) {
    innerClassCount = (int) value;
  }

  public void setNumberOfFiles(long value) {
    numberOfFiles = (int) value;
  }

  private void setOptions(SegmentOptions options) {
    this.options = options;
  }

  public void setSegmentsRemaining(long value) {
    segmentsRemaining = (int) value;
  }

  /**
   * This is only here to provide a mechanism to turn off the warnings (and to
   * prevent anyone from accidentally removing them from the file)
   *
   * @deprecated this will be deleted in the future, once I've started to use
   *             them
   *
   */
  int shutUpAboutTheStupidNotReadVariablesYetIHaventImplementedIt() {
    return archiveMajor + archiveMinor + cpLong.hashCode()
        + icName.hashCode() + icOuterClass.hashCode()
        + icThisClass.hashCode();
  }

  /**
   * 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 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 entries (e.g. further segments) to be written.
   *
   * @param out
   *            the JarOutputStream to write data to
   * @param in
   *            the same InputStream that was used to parse the segment
   * @throws IOException
   *             if an error occurs whilst reading or writing to the streams
   * @throws Pack200Exception
   *             if an error occurs whilst unpacking data
   */
  public void writeJar(JarOutputStream out, InputStream in)
      throws IOException, Pack200Exception {
    processFileBits(in);
    DataOutputStream dos = new DataOutputStream(out);
    // out.setLevel(JarEntry.DEFLATED)
    // now write the files out
    int classNum = 0;
    for (int i = 0; i < numberOfFiles; i++) {
      String name = fileName[i];
      long modtime = archiveModtime + fileModtime[i];
      boolean deflate = (fileOptions[i] & 1) == 1
          || options.shouldDeflate();
      boolean isClass = (fileOptions[i] & 2) == 2 || name == null
          || name.equals("");
      if (isClass) {
        // pull from headers
        if (name == null || name.equals(""))
          name = classThis[classNum] + ".class";
      }
      JarEntry entry = new JarEntry(name);
      if (deflate)
        entry.setMethod(JarEntry.DEFLATED);
      entry.setTime(modtime);
      out.putNextEntry(entry);

      if (isClass) {
        // write to dos
        ClassFile classFile = buildClassFile(classNum);
        classFile.write(dos);
        dos.flush();
        classNum++;
      } else {
        long size = fileSize[i];
        entry.setSize(size);
        // TODO pull from in
        byte[] data = fileBits[i];
        out.write(data);
      }
    }
    dos.flush();
    out.finish();
    out.flush();
  }
}
TOP

Related Classes of org.apache.harmony.pack200.Segment$SegmentConstantPool

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.