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