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