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}