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}