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}