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