    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.commons.compress.harmony.unpack200;
    018
    019import java.io.ByteArrayInputStream;
    020import java.io.EOFException;
    021import java.io.IOException;
    022import java.io.InputStream;
    023
    024import org.apache.commons.compress.harmony.pack200.BHSDCodec;
    025import org.apache.commons.compress.harmony.pack200.Codec;
    026import org.apache.commons.compress.harmony.pack200.Pack200Exception;
    027
    028/**
    029 * SegmentHeader is the header band of a {@link Segment}
    030 */
    031public class SegmentHeader {
    032
    033    private int archiveMajor;
    034
    035    private int archiveMinor;
    036
    037    private long archiveModtime;
    038
    039    private long archiveSize;
    040
    041    private int attributeDefinitionCount;
    042
    043    private InputStream bandHeadersInputStream;
    044
    045    private int bandHeadersSize;
    046
    047    private int classCount;
    048
    049    private int cpClassCount;
    050
    051    private int cpDescriptorCount;
    052
    053    private int cpDoubleCount;
    054
    055    private int cpFieldCount;
    056
    057    private int cpFloatCount;
    058
    059    private int cpIMethodCount;
    060
    061    private int cpIntCount;
    062
    063    private int cpLongCount;
    064
    065    private int cpMethodCount;
    066
    067    private int cpSignatureCount;
    068
    069    private int cpStringCount;
    070
    071    private int cpUTF8Count;
    072
    073    private int defaultClassMajorVersion;
    074
    075    private int defaultClassMinorVersion;
    076
    077    private int innerClassCount;
    078
    079    private int numberOfFiles;
    080
    081    private int segmentsRemaining;
    082
    083    private SegmentOptions options;
    084
    085    private final Segment segment;
    086
    087    /**
    088     * The magic header for a Pack200 Segment is 0xCAFED00D. I wonder where they get their inspiration from ...
    089     */
    090    private static final int[] magic = {0xCA, 0xFE, 0xD0, 0x0D};
    091
    092    public SegmentHeader(final Segment segment) {
    093        this.segment = segment;
    094    }
    095
    096    public int getArchiveSizeOffset() {
    097        return archiveSizeOffset;
    098    }
    099
    100    private int archiveSizeOffset;
    101
    102    public void read(final InputStream in) throws IOException, Pack200Exception, Error, Pack200Exception {
    103
    104        final int word[] = decodeScalar("archive_magic_word", in, Codec.BYTE1, magic.length);
    105        for (int m = 0; m < magic.length; m++) {
    106            if (word[m] != magic[m]) {
    107                throw new Error("Bad header");
    108            }
    109        }
    110        setArchiveMinorVersion(decodeScalar("archive_minver", in, Codec.UNSIGNED5));
    111        setArchiveMajorVersion(decodeScalar("archive_majver", in, Codec.UNSIGNED5));
    112        options = new SegmentOptions(decodeScalar("archive_options", in, Codec.UNSIGNED5));
    113        parseArchiveFileCounts(in);
    114        parseArchiveSpecialCounts(in);
    115        parseCpCounts(in);
    116        parseClassCounts(in);
    117
    118        if (getBandHeadersSize() > 0) {
    119            final byte[] bandHeaders = new byte[getBandHeadersSize()];
    120            readFully(in, bandHeaders);
    121            setBandHeadersData(bandHeaders);
    122        }
    123
    124        archiveSizeOffset = archiveSizeOffset - in.available();
    125    }
    126
    127    public void unpack() {
    128
    129    }
    130
    131    /**
    132     * Sets the minor version of this archive
    133     *
    134     * @param version the minor version of the archive
    135     * @throws Pack200Exception if the minor version is not 7
    136     */
    137    private void setArchiveMinorVersion(final int version) throws Pack200Exception {
    138        if (version != 7) {
    139            throw new Pack200Exception("Invalid segment minor version");
    140        }
    141        archiveMinor = version;
    142    }
    143
    144    /**
    145     * Sets the major version of this archive.
    146     *
    147     * @param version the minor version of the archive
    148     * @throws Pack200Exception if the major version is not 150
    149     */
    150    private void setArchiveMajorVersion(final int version) throws Pack200Exception {
    151        if (version != 150) {
    152            throw new Pack200Exception("Invalid segment major version: " + version);
    153        }
    154        archiveMajor = version;
    155    }
    156
    157    public long getArchiveModtime() {
    158        return archiveModtime;
    159    }
    160
    161    public int getAttributeDefinitionCount() {
    162        return attributeDefinitionCount;
    163    }
    164
    165    public int getClassCount() {
    166        return classCount;
    167    }
    168
    169    public int getCpClassCount() {
    170        return cpClassCount;
    171    }
    172
    173    public int getCpDescriptorCount() {
    174        return cpDescriptorCount;
    175    }
    176
    177    public int getCpDoubleCount() {
    178        return cpDoubleCount;
    179    }
    180
    181    public int getCpFieldCount() {
    182        return cpFieldCount;
    183    }
    184
    185    public int getCpFloatCount() {
    186        return cpFloatCount;
    187    }
    188
    189    public int getCpIMethodCount() {
    190        return cpIMethodCount;
    191    }
    192
    193    public int getCpIntCount() {
    194        return cpIntCount;
    195    }
    196
    197    public int getCpLongCount() {
    198        return cpLongCount;
    199    }
    200
    201    public int getCpMethodCount() {
    202        return cpMethodCount;
    203    }
    204
    205    public int getCpSignatureCount() {
    206        return cpSignatureCount;
    207    }
    208
    209    public int getCpStringCount() {
    210        return cpStringCount;
    211    }
    212
    213    public int getCpUTF8Count() {
    214        return cpUTF8Count;
    215    }
    216
    217    public int getDefaultClassMajorVersion() {
    218        return defaultClassMajorVersion;
    219    }
    220
    221    public int getDefaultClassMinorVersion() {
    222        return defaultClassMinorVersion;
    223    }
    224
    225    public int getInnerClassCount() {
    226        return innerClassCount;
    227    }
    228
    229    public long getArchiveSize() {
    230        return archiveSize;
    231    }
    232
    233    /**
    234     * Obtain the band headers data as an input stream. If no band headers are present, this will return an empty input
    235     * stream to prevent any further reads taking place.
    236     *
    237     * Note that as a stream, data consumed from this input stream can't be re-used. Data is only read from this stream
    238     * if the encoding is such that additional information needs to be decoded from the stream itself.
    239     *
    240     * @return the band headers input stream
    241     */
    242    public InputStream getBandHeadersInputStream() {
    243        if (bandHeadersInputStream == null) {
    244            bandHeadersInputStream = new ByteArrayInputStream(new byte[0]);
    245        }
    246        return bandHeadersInputStream;
    247
    248    }
    249
    250    public int getNumberOfFiles() {
    251        return numberOfFiles;
    252    }
    253
    254    public int getSegmentsRemaining() {
    255        return segmentsRemaining;
    256    }
    257
    258    public SegmentOptions getOptions() {
    259        return options;
    260    }
    261
    262    private void parseArchiveFileCounts(final InputStream in) throws IOException, Pack200Exception {
    263        if (options.hasArchiveFileCounts()) {
    264            setArchiveSize((long) decodeScalar("archive_size_hi", in, Codec.UNSIGNED5) << 32 |
    265                decodeScalar("archive_size_lo", in, Codec.UNSIGNED5));
    266            archiveSizeOffset = in.available();
    267            setSegmentsRemaining(decodeScalar("archive_next_count", in, Codec.UNSIGNED5));
    268            setArchiveModtime(decodeScalar("archive_modtime", in, Codec.UNSIGNED5));
    269            numberOfFiles = decodeScalar("file_count", in, Codec.UNSIGNED5);
    270        }
    271    }
    272
    273    private void parseArchiveSpecialCounts(final InputStream in) throws IOException, Pack200Exception {
    274        if (getOptions().hasSpecialFormats()) {
    275            bandHeadersSize = decodeScalar("band_headers_size", in, Codec.UNSIGNED5);
    276            setAttributeDefinitionCount(decodeScalar("attr_definition_count", in, Codec.UNSIGNED5));
    277        }
    278    }
    279
    280    private void parseClassCounts(final InputStream in) throws IOException, Pack200Exception {
    281        innerClassCount = decodeScalar("ic_count", in, Codec.UNSIGNED5);
    282        defaultClassMinorVersion = decodeScalar("default_class_minver", in, Codec.UNSIGNED5);
    283        defaultClassMajorVersion = decodeScalar("default_class_majver", in, Codec.UNSIGNED5);
    284        classCount = decodeScalar("class_count", in, Codec.UNSIGNED5);
    285    }
    286
    287    private void parseCpCounts(final InputStream in) throws IOException, Pack200Exception {
    288        cpUTF8Count = decodeScalar("cp_Utf8_count", in, Codec.UNSIGNED5);
    289        if (getOptions().hasCPNumberCounts()) {
    290            cpIntCount = decodeScalar("cp_Int_count", in, Codec.UNSIGNED5);
    291            cpFloatCount = decodeScalar("cp_Float_count", in, Codec.UNSIGNED5);
    292            cpLongCount = decodeScalar("cp_Long_count", in, Codec.UNSIGNED5);
    293            cpDoubleCount = decodeScalar("cp_Double_count", in, Codec.UNSIGNED5);
    294        }
    295        cpStringCount = decodeScalar("cp_String_count", in, Codec.UNSIGNED5);
    296        cpClassCount = decodeScalar("cp_Class_count", in, Codec.UNSIGNED5);
    297        cpSignatureCount = decodeScalar("cp_Signature_count", in, Codec.UNSIGNED5);
    298        cpDescriptorCount = decodeScalar("cp_Descr_count", in, Codec.UNSIGNED5);
    299        cpFieldCount = decodeScalar("cp_Field_count", in, Codec.UNSIGNED5);
    300        cpMethodCount = decodeScalar("cp_Method_count", in, Codec.UNSIGNED5);
    301        cpIMethodCount = decodeScalar("cp_Imethod_count", in, Codec.UNSIGNED5);
    302    }
    303
    304    /**
    305     * Decode a number of scalars from the band file. A scalar is like a band, but does not perform any band code
    306     * switching.
    307     *
    308     * @param name the name of the scalar (primarily for logging/debugging purposes)
    309     * @param in the input stream to read from
    310     * @param codec the codec for this scalar
    311     * @return an array of decoded <code>long[]</code> values
    312     * @throws IOException if there is a problem reading from the underlying input stream
    313     * @throws Pack200Exception if there is a problem decoding the value or that the value is invalid
    314     */
    315    private int[] decodeScalar(final String name, final InputStream in, final BHSDCodec codec, final int n)
    316        throws IOException, Pack200Exception {
    317        segment.log(Segment.LOG_LEVEL_VERBOSE, "Parsed #" + name + " (" + n + ")");
    318        return codec.decodeInts(n, in);
    319    }
    320
    321    /**
    322     * Decode a scalar from the band file. A scalar is like a band, but does not perform any band code switching.
    323     *
    324     * @param name the name of the scalar (primarily for logging/debugging purposes)
    325     * @param in the input stream to read from
    326     * @param codec the codec for this scalar
    327     * @return the decoded value
    328     * @throws IOException if there is a problem reading from the underlying input stream
    329     * @throws Pack200Exception if there is a problem decoding the value or that the value is invalid
    330     */
    331    private int decodeScalar(final String name, final InputStream in, final BHSDCodec codec)
    332        throws IOException, Pack200Exception {
    333        final int ret = codec.decode(in);
    334        segment.log(Segment.LOG_LEVEL_VERBOSE, "Parsed #" + name + " as " + ret);
    335        return ret;
    336    }
    337
    338    public void setArchiveModtime(final long archiveModtime) {
    339        this.archiveModtime = archiveModtime;
    340    }
    341
    342    public void setArchiveSize(final long archiveSize) {
    343        this.archiveSize = archiveSize;
    344    }
    345
    346    private void setAttributeDefinitionCount(final long valuie) {
    347        this.attributeDefinitionCount = (int) valuie;
    348    }
    349
    350    private void setBandHeadersData(final byte[] bandHeaders) {
    351        this.bandHeadersInputStream = new ByteArrayInputStream(bandHeaders);
    352    }
    353
    354    public void setSegmentsRemaining(final long value) {
    355        segmentsRemaining = (int) value;
    356    }
    357
    358    /**
    359     * Completely reads in a byte array, akin to the implementation in {@link java.lang.DataInputStream}. TODO Refactor
    360     * out into a separate InputStream handling class
    361     *
    362     * @param in the input stream to read from
    363     * @param data the byte array to read into
    364     * @throws IOException if a problem occurs during reading from the underlying stream
    365     * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec
    366     */
    367    private void readFully(final InputStream in, final byte[] data) throws IOException, Pack200Exception {
    368        int total = in.read(data);
    369        if (total == -1) {
    370            throw new EOFException("Failed to read any data from input stream");
    371        }
    372        while (total < data.length) {
    373            final int delta = in.read(data, total, data.length - total);
    374            if (delta == -1) {
    375                throw new EOFException("Failed to read some data from input stream");
    376            }
    377            total += delta;
    378        }
    379    }
    380
    381    public int getBandHeadersSize() {
    382        return bandHeadersSize;
    383    }
    384}