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