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 <i>count</i> 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 <i>count</i> 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}