    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.IOException;
    020import java.io.InputStream;
    021import java.util.Arrays;
    022
    023import org.apache.commons.compress.harmony.pack200.BHSDCodec;
    024import org.apache.commons.compress.harmony.pack200.Codec;
    025import org.apache.commons.compress.harmony.pack200.CodecEncoding;
    026import org.apache.commons.compress.harmony.pack200.Pack200Exception;
    027import org.apache.commons.compress.harmony.pack200.PopulationCodec;
    028import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
    029import org.apache.commons.compress.harmony.unpack200.bytecode.CPDouble;
    030import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef;
    031import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat;
    032import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger;
    033import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef;
    034import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong;
    035import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef;
    036import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
    037import org.apache.commons.compress.harmony.unpack200.bytecode.CPString;
    038import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
    039import org.apache.commons.compress.utils.ExactMath;
    040
    041/**
    042 * Abstract superclass for a set of bands
    043 */
    044public abstract class BandSet {
    045
    046    protected Segment segment;
    047
    048    protected SegmentHeader header;
    049
    050    public BandSet(final Segment segment) {
    051        this.segment = segment;
    052        this.header = segment.getSegmentHeader();
    053    }
    054
    055    /**
    056     * Decode a band and return an array of {@code int} values
    057     *
    058     * @param name the name of the band (primarily for logging/debugging purposes)
    059     * @param in the InputStream to decode from
    060     * @param codec the default Codec for this band
    061     * @param count the number of elements to read
    062     * @return an array of decoded {@code int} values
    063     * @throws IOException if there is a problem reading from the underlying input stream
    064     * @throws Pack200Exception if there is a problem decoding the value or that the value is invalid
    065     */
    066    public int[] decodeBandInt(final String name, final InputStream in, final BHSDCodec codec, final int count)
    067        throws IOException, Pack200Exception {
    068        int[] band;
    069        // Useful for debugging
    070//        if (count > 0) {
    071//            System.out.println("decoding " + name + " " + count);
    072//        }
    073        Codec codecUsed = codec;
    074        if (codec.getB() == 1 || count == 0) {
    075            return codec.decodeInts(count, in);
    076        }
    077        final int[] getFirst = codec.decodeInts(1, in);
    078        if (getFirst.length == 0) {
    079            return getFirst;
    080        }
    081        final int first = getFirst[0];
    082        if (codec.isSigned() && first >= -256 && first <= -1) {
    083            // Non-default codec should be used
    084            codecUsed = CodecEncoding.getCodec((-1 - first), header.getBandHeadersInputStream(), codec);
    085            band = codecUsed.decodeInts(count, in);
    086        } else if (!codec.isSigned() && first >= codec.getL() && first <= codec.getL() + 255) {
    087            // Non-default codec should be used
    088            codecUsed = CodecEncoding.getCodec(first - codec.getL(), header.getBandHeadersInputStream(), codec);
    089            band = codecUsed.decodeInts(count, in);
    090        } else {
    091            // First element should not be discarded
    092            band = codec.decodeInts(count - 1, in, first);
    093        }
    094        // Useful for debugging -E options:
    095        // if (!codecUsed.equals(codec)) {
    096        // int bytes = codecUsed.lastBandLength;
    097        // System.out.println(count + " " + name + " encoded with " + codecUsed + " " + bytes);
    098        // }
    099        if (codecUsed instanceof PopulationCodec) {
    100            final PopulationCodec popCodec = (PopulationCodec) codecUsed;
    101            final int[] favoured = popCodec.getFavoured().clone();
    102            Arrays.sort(favoured);
    103            for (int i = 0; i < band.length; i++) {
    104                final boolean favouredValue = Arrays.binarySearch(favoured, band[i]) > -1;
    105                final Codec theCodec = favouredValue ? popCodec.getFavouredCodec() : popCodec.getUnfavouredCodec();
    106                if (theCodec instanceof BHSDCodec && ((BHSDCodec) theCodec).isDelta()) {
    107                    final BHSDCodec bhsd = (BHSDCodec) theCodec;
    108                    final long cardinality = bhsd.cardinality();
    109                    while (band[i] > bhsd.largest()) {
    110                        band[i] -= cardinality;
    111                    }
    112                    while (band[i] < bhsd.smallest()) {
    113                        band[i] = ExactMath.add(band[i], cardinality);
    114                    }
    115                }
    116            }
    117        }
    118        return band;
    119    }
    120
    121    /**
    122     * Decode a band and return an array of {@code int[]} values
    123     *
    124     * @param name the name of the band (primarily for logging/debugging purposes)
    125     * @param in the InputStream to decode from
    126     * @param defaultCodec the default codec for this band
    127     * @param counts the numbers of elements to read for each int array within the array to be returned
    128     * @return an array of decoded {@code int[]} values
    129     * @throws IOException if there is a problem reading from the underlying input stream
    130     * @throws Pack200Exception if there is a problem decoding the value or that the value is invalid
    131     */
    132    public int[][] decodeBandInt(final String name, final InputStream in, final BHSDCodec defaultCodec,
    133        final int[] counts) throws IOException, Pack200Exception {
    134        final int[][] result = new int[counts.length][];
    135        int totalCount = 0;
    136        for (final int count : counts) {
    137            totalCount += count;
    138        }
    139        final int[] twoDResult = decodeBandInt(name, in, defaultCodec, totalCount);
    140        int index = 0;
    141        for (int i = 0; i < result.length; i++) {
    142            result[i] = new int[counts[i]];
    143            for (int j = 0; j < result[i].length; j++) {
    144                result[i][j] = twoDResult[index];
    145                index++;
    146            }
    147        }
    148        return result;
    149    }
    150
    151    protected String[] getReferences(final int[] ints, final String[] reference) {
    152        final String[] result = new String[ints.length];
    153        Arrays.setAll(result, i -> reference[ints[i]]);
    154        return result;
    155    }
    156
    157    protected String[][] getReferences(final int[][] ints, final String[] reference) {
    158        final String[][] result = new String[ints.length][];
    159        for (int i = 0; i < result.length; i++) {
    160            result[i] = new String[ints[i].length];
    161            for (int j = 0; j < result[i].length; j++) {
    162                result[i][j] = reference[ints[i][j]];
    163            }
    164        }
    165        return result;
    166    }
    167
    168    public CPClass[] parseCPClassReferences(final String name, final InputStream in, final BHSDCodec codec,
    169        final int count) throws IOException, Pack200Exception {
    170        final int[] indices = decodeBandInt(name, in, codec, count);
    171        final CPClass[] result = new CPClass[indices.length];
    172        for (int i1 = 0; i1 < count; i1++) {
    173            result[i1] = segment.getCpBands().cpClassValue(indices[i1]);
    174        }
    175        return result;
    176    }
    177
    178    public CPNameAndType[] parseCPDescriptorReferences(final String name, final InputStream in, final BHSDCodec codec,
    179        final int count) throws IOException, Pack200Exception {
    180        final CpBands cpBands = segment.getCpBands();
    181        final int[] indices = decodeBandInt(name, in, codec, count);
    182        final CPNameAndType[] result = new CPNameAndType[indices.length];
    183        for (int i1 = 0; i1 < count; i1++) {
    184            final int index = indices[i1];
    185            result[i1] = cpBands.cpNameAndTypeValue(index);
    186        }
    187        return result;
    188    }
    189
    190    public CPDouble[] parseCPDoubleReferences(final String name, final InputStream in, final BHSDCodec codec,
    191        final int count) throws IOException, Pack200Exception {
    192        final int[] indices = decodeBandInt(name, in, codec, count);
    193        final CPDouble[] result = new CPDouble[indices.length];
    194        for (int i1 = 0; i1 < count; i1++) {
    195            result[i1] = segment.getCpBands().cpDoubleValue(indices[i1]);
    196        }
    197        return result;
    198    }
    199
    200    public CPFieldRef[] parseCPFieldRefReferences(final String name, final InputStream in, final BHSDCodec codec,
    201        final int count) throws IOException, Pack200Exception {
    202        final CpBands cpBands = segment.getCpBands();
    203        final int[] indices = decodeBandInt(name, in, codec, count);
    204        final CPFieldRef[] result = new CPFieldRef[indices.length];
    205        for (int i1 = 0; i1 < count; i1++) {
    206            final int index = indices[i1];
    207            result[i1] = cpBands.cpFieldValue(index);
    208        }
    209        return result;
    210    }
    211
    212    public CPFloat[] parseCPFloatReferences(final String name, final InputStream in, final BHSDCodec codec,
    213        final int count) throws IOException, Pack200Exception {
    214        final int[] indices = decodeBandInt(name, in, codec, count);
    215        final CPFloat[] result = new CPFloat[indices.length];
    216        for (int i1 = 0; i1 < count; i1++) {
    217            result[i1] = segment.getCpBands().cpFloatValue(indices[i1]);
    218        }
    219        return result;
    220    }
    221
    222    public CPInterfaceMethodRef[] parseCPInterfaceMethodRefReferences(final String name, final InputStream in,
    223        final BHSDCodec codec, final int count) throws IOException, Pack200Exception {
    224        final CpBands cpBands = segment.getCpBands();
    225        final int[] indices = decodeBandInt(name, in, codec, count);
    226        final CPInterfaceMethodRef[] result = new CPInterfaceMethodRef[indices.length];
    227        for (int i1 = 0; i1 < count; i1++) {
    228            result[i1] = cpBands.cpIMethodValue(indices[i1]);
    229        }
    230        return result;
    231    }
    232
    233    public CPInteger[] parseCPIntReferences(final String name, final InputStream in, final BHSDCodec codec,
    234        final int count) throws IOException, Pack200Exception {
    235        final int[] reference = segment.getCpBands().getCpInt();
    236        final int[] indices = decodeBandInt(name, in, codec, count);
    237        final CPInteger[] result = new CPInteger[indices.length];
    238        for (int i1 = 0; i1 < count; i1++) {
    239            final int index = indices[i1];
    240            if (index < 0 || index >= reference.length) {
    241                throw new Pack200Exception("Something has gone wrong during parsing references, index = " + index
    242                    + ", array size = " + reference.length);
    243            }
    244            result[i1] = segment.getCpBands().cpIntegerValue(index);
    245        }
    246        return result;
    247    }
    248
    249    public CPLong[] parseCPLongReferences(final String name, final InputStream in, final BHSDCodec codec,
    250        final int count) throws IOException, Pack200Exception {
    251        final long[] reference = segment.getCpBands().getCpLong();
    252        final int[] indices = decodeBandInt(name, in, codec, count);
    253        final CPLong[] result = new CPLong[indices.length];
    254        for (int i1 = 0; i1 < count; i1++) {
    255            final int index = indices[i1];
    256            if (index < 0 || index >= reference.length) {
    257                throw new Pack200Exception("Something has gone wrong during parsing references, index = " + index
    258                    + ", array size = " + reference.length);
    259            }
    260            result[i1] = segment.getCpBands().cpLongValue(index);
    261        }
    262        return result;
    263    }
    264
    265    public CPMethodRef[] parseCPMethodRefReferences(final String name, final InputStream in, final BHSDCodec codec,
    266        final int count) throws IOException, Pack200Exception {
    267        final CpBands cpBands = segment.getCpBands();
    268        final int[] indices = decodeBandInt(name, in, codec, count);
    269        final CPMethodRef[] result = new CPMethodRef[indices.length];
    270        for (int i1 = 0; i1 < count; i1++) {
    271            result[i1] = cpBands.cpMethodValue(indices[i1]);
    272        }
    273        return result;
    274    }
    275
    276    public CPUTF8[] parseCPSignatureReferences(final String name, final InputStream in, final BHSDCodec codec,
    277        final int count) throws IOException, Pack200Exception {
    278        final int[] indices = decodeBandInt(name, in, codec, count);
    279        final CPUTF8[] result = new CPUTF8[indices.length];
    280        for (int i1 = 0; i1 < count; i1++) {
    281            result[i1] = segment.getCpBands().cpSignatureValue(indices[i1]);
    282        }
    283        return result;
    284    }
    285
    286    protected CPUTF8[][] parseCPSignatureReferences(final String name, final InputStream in, final BHSDCodec codec,
    287        final int[] counts) throws IOException, Pack200Exception {
    288        final CPUTF8[][] result = new CPUTF8[counts.length][];
    289        int sum = 0;
    290        for (int i = 0; i < counts.length; i++) {
    291            result[i] = new CPUTF8[counts[i]];
    292            sum += counts[i];
    293        }
    294        final CPUTF8[] result1 = new CPUTF8[sum];
    295        final int[] indices = decodeBandInt(name, in, codec, sum);
    296        for (int i1 = 0; i1 < sum; i1++) {
    297            result1[i1] = segment.getCpBands().cpSignatureValue(indices[i1]);
    298        }
    299        int pos = 0;
    300        for (int i = 0; i < counts.length; i++) {
    301            final int num = counts[i];
    302            result[i] = new CPUTF8[num];
    303            System.arraycopy(result1, pos, result[i], 0, num);
    304            pos += num;
    305        }
    306        return result;
    307    }
    308
    309    public CPString[] parseCPStringReferences(final String name, final InputStream in, final BHSDCodec codec,
    310        final int count) throws IOException, Pack200Exception {
    311        final int[] indices = decodeBandInt(name, in, codec, count);
    312        final CPString[] result = new CPString[indices.length];
    313        for (int i1 = 0; i1 < count; i1++) {
    314            result[i1] = segment.getCpBands().cpStringValue(indices[i1]);
    315        }
    316        return result;
    317    }
    318
    319    public CPUTF8[] parseCPUTF8References(final String name, final InputStream in, final BHSDCodec codec,
    320        final int count) throws IOException, Pack200Exception {
    321        final int[] indices = decodeBandInt(name, in, codec, count);
    322        final CPUTF8[] result = new CPUTF8[indices.length];
    323        for (int i1 = 0; i1 < count; i1++) {
    324            final int index = indices[i1];
    325            result[i1] = segment.getCpBands().cpUTF8Value(index);
    326        }
    327        return result;
    328    }
    329
    330    public CPUTF8[][] parseCPUTF8References(final String name, final InputStream in, final BHSDCodec codec,
    331        final int[] counts) throws IOException, Pack200Exception {
    332        final CPUTF8[][] result = new CPUTF8[counts.length][];
    333        int sum = 0;
    334        for (int i = 0; i < counts.length; i++) {
    335            result[i] = new CPUTF8[counts[i]];
    336            sum += counts[i];
    337        }
    338        final CPUTF8[] result1 = new CPUTF8[sum];
    339        final int[] indices = decodeBandInt(name, in, codec, sum);
    340        for (int i1 = 0; i1 < sum; i1++) {
    341            final int index = indices[i1];
    342            result1[i1] = segment.getCpBands().cpUTF8Value(index);
    343        }
    344        int pos = 0;
    345        for (int i = 0; i < counts.length; i++) {
    346            final int num = counts[i];
    347            result[i] = new CPUTF8[num];
    348            System.arraycopy(result1, pos, result[i], 0, num);
    349            pos += num;
    350        }
    351        return result;
    352    }
    353
    354    public long[] parseFlags(final String name, final InputStream in, final int count, final BHSDCodec hiCodec,
    355        final BHSDCodec loCodec) throws IOException, Pack200Exception {
    356        return parseFlags(name, in, new int[] {count}, hiCodec, loCodec)[0];
    357    }
    358
    359    public long[] parseFlags(final String name, final InputStream in, final int count, final BHSDCodec codec,
    360        final boolean hasHi) throws IOException, Pack200Exception {
    361        return parseFlags(name, in, new int[] {count}, (hasHi ? codec : null), codec)[0];
    362    }
    363
    364    public long[][] parseFlags(final String name, final InputStream in, final int[] counts, final BHSDCodec hiCodec,
    365        final BHSDCodec loCodec) throws IOException, Pack200Exception {
    366        final int count = counts.length;
    367        if (count == 0) {
    368            return new long[][] {{}};
    369        }
    370        int sum = 0;
    371        final long[][] result = new long[count][];
    372        for (int i = 0; i < count; i++) {
    373            result[i] = new long[counts[i]];
    374            sum += counts[i];
    375        }
    376        int[] hi = null;
    377        int[] lo;
    378        if (hiCodec != null) {
    379            hi = decodeBandInt(name, in, hiCodec, sum);
    380        }
    381        lo = decodeBandInt(name, in, loCodec, sum);
    382
    383        int index = 0;
    384        for (int i = 0; i < result.length; i++) {
    385            for (int j = 0; j < result[i].length; j++) {
    386                if (hi != null) {
    387                    result[i][j] = ((long) hi[index] << 32) | (lo[index] & 4294967295L);
    388                } else {
    389                    result[i][j] = lo[index];
    390                }
    391                index++;
    392            }
    393        }
    394        return result;
    395    }
    396
    397    public long[][] parseFlags(final String name, final InputStream in, final int[] counts, final BHSDCodec codec,
    398        final boolean hasHi) throws IOException, Pack200Exception {
    399        return parseFlags(name, in, counts, (hasHi ? codec : null), codec);
    400    }
    401
    402    /**
    403     * Parses <i>count</i> references from {@code in}, using {@code codec} to decode the values as indexes
    404     * into {@code reference} (which is populated prior to this call). An exception is thrown if a decoded index
    405     * falls outside the range [0..reference.length-1].
    406     *
    407     * @param name the band name
    408     * @param in the input stream to read from
    409     * @param codec the BHSDCodec to use for decoding
    410     * @param count the number of references to decode
    411     * @param reference the array of values to use for the references
    412     * @return Parsed references.
    413     *
    414     * @throws IOException if a problem occurs during reading from the underlying stream
    415     * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported Codec
    416     */
    417    public String[] parseReferences(final String name, final InputStream in, final BHSDCodec codec, final int count,
    418        final String[] reference) throws IOException, Pack200Exception {
    419        return parseReferences(name, in, codec, new int[] {count}, reference)[0];
    420    }
    421
    422    /**
    423     * Parses <i>count</i> references from {@code in}, using {@code codec} to decode the values as indexes
    424     * into {@code reference} (which is populated prior to this call). An exception is thrown if a decoded index
    425     * falls outside the range [0..reference.length-1]. Unlike the other parseReferences, this post-processes the result
    426     * into an array of results.
    427     *
    428     * @param name TODO
    429     * @param in the input stream to read from
    430     * @param codec the BHSDCodec to use for decoding
    431     * @param counts the numbers of references to decode for each array entry
    432     * @param reference the array of values to use for the references
    433     * @return Parsed references.
    434     *
    435     * @throws IOException if a problem occurs during reading from the underlying stream
    436     * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported Codec
    437     */
    438    public String[][] parseReferences(final String name, final InputStream in, final BHSDCodec codec,
    439        final int[] counts, final String[] reference) throws IOException, Pack200Exception {
    440        final int count = counts.length;
    441        if (count == 0) {
    442            return new String[][] {{}};
    443        }
    444        final String[][] result = new String[count][];
    445        int sum = 0;
    446        for (int i = 0; i < count; i++) {
    447            result[i] = new String[counts[i]];
    448            sum += counts[i];
    449        }
    450        // TODO Merge the decode and parsing of a multiple structure into one
    451        final String[] result1 = new String[sum];
    452        final int[] indices = decodeBandInt(name, in, codec, sum);
    453        for (int i1 = 0; i1 < sum; i1++) {
    454            final int index = indices[i1];
    455            if (index < 0 || index >= reference.length) {
    456                throw new Pack200Exception("Something has gone wrong during parsing references, index = " + index
    457                    + ", array size = " + reference.length);
    458            }
    459            result1[i1] = reference[index];
    460        }
    461        // TODO Merge the decode and parsing of a multiple structure into one
    462        int pos = 0;
    463        for (int i = 0; i < count; i++) {
    464            final int num = counts[i];
    465            result[i] = new String[num];
    466            System.arraycopy(result1, pos, result[i], 0, num);
    467            pos += num;
    468        }
    469        return result;
    470    }
    471
    472    public abstract void read(InputStream inputStream) throws IOException, Pack200Exception;
    473
    474    public abstract void unpack() throws IOException, Pack200Exception;
    475
    476    public void unpack(final InputStream in) throws IOException, Pack200Exception {
    477        read(in);
    478        unpack();
    479    }
    480
    481}