001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.harmony.pack200;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.Arrays;
024
025import org.apache.commons.compress.utils.ExactMath;
026
027/**
028 * A run codec is a grouping of two nested codecs; K values are decoded from the first codec, and the remaining codes are decoded from the remaining codec. Note
029 * that since this codec maintains state, the instances are not reusable.
030 */
031public class RunCodec extends Codec {
032
033    private int k;
034    private final Codec aCodec;
035    private final Codec bCodec;
036    private int last;
037
038    public RunCodec(final int k, final Codec aCodec, final Codec bCodec) throws Pack200Exception {
039        if (k <= 0) {
040            throw new Pack200Exception("Cannot have a RunCodec for a negative number of numbers");
041        }
042        if (aCodec == null || bCodec == null) {
043            throw new Pack200Exception("Must supply both codecs for a RunCodec");
044        }
045        this.k = k;
046        this.aCodec = aCodec;
047        this.bCodec = bCodec;
048    }
049
050    @Override
051    public int decode(final InputStream in) throws IOException, Pack200Exception {
052        return decode(in, this.last);
053    }
054
055    @Override
056    public int decode(final InputStream in, final long last) throws IOException, Pack200Exception {
057        if (--k >= 0) {
058            final int value = aCodec.decode(in, this.last);
059            this.last = k == 0 ? 0 : value;
060            return normalise(value, aCodec);
061        }
062        this.last = bCodec.decode(in, this.last);
063        return normalise(this.last, bCodec);
064    }
065
066    @Override
067    public int[] decodeInts(final int n, final InputStream in) throws IOException, Pack200Exception {
068        final int[] aValues = aCodec.decodeInts(k, in);
069        normalise(aValues, aCodec);
070        final int[] bValues = bCodec.decodeInts(n - k, in);
071        normalise(bValues, bCodec);
072        final int[] band = new int[check(n, in)];
073        System.arraycopy(aValues, 0, band, 0, k);
074        System.arraycopy(bValues, 0, band, k, n - k);
075        lastBandLength = aCodec.lastBandLength + bCodec.lastBandLength;
076        return band;
077    }
078
079    @Override
080    public byte[] encode(final int value) throws Pack200Exception {
081        throw new Pack200Exception("Must encode entire band at once with a RunCodec");
082    }
083
084    @Override
085    public byte[] encode(final int value, final int last) throws Pack200Exception {
086        throw new Pack200Exception("Must encode entire band at once with a RunCodec");
087    }
088
089    public Codec getACodec() {
090        return aCodec;
091    }
092
093    public Codec getBCodec() {
094        return bCodec;
095    }
096
097    public int getK() {
098        return k;
099    }
100
101    private int normalise(int value, final Codec codecUsed) {
102        if (codecUsed instanceof BHSDCodec) {
103            final BHSDCodec bhsd = (BHSDCodec) codecUsed;
104            if (bhsd.isDelta()) {
105                final long cardinality = bhsd.cardinality();
106                while (value > bhsd.largest()) {
107                    value -= cardinality;
108                }
109                while (value < bhsd.smallest()) {
110                    value = ExactMath.add(value, cardinality);
111                }
112            }
113        }
114        return value;
115    }
116
117    private void normalise(final int[] band, final Codec codecUsed) {
118        if (codecUsed instanceof BHSDCodec) {
119            final BHSDCodec bhsd = (BHSDCodec) codecUsed;
120            if (bhsd.isDelta()) {
121                final long cardinality = bhsd.cardinality();
122                for (int i = 0; i < band.length; i++) {
123                    while (band[i] > bhsd.largest()) {
124                        band[i] -= cardinality;
125                    }
126                    while (band[i] < bhsd.smallest()) {
127                        band[i] = ExactMath.add(band[i], cardinality);
128                    }
129                }
130            }
131        } else if (codecUsed instanceof PopulationCodec) {
132            final PopulationCodec popCodec = (PopulationCodec) codecUsed;
133            final int[] favoured = popCodec.getFavoured().clone();
134            Arrays.sort(favoured);
135            for (int i = 0; i < band.length; i++) {
136                final boolean favouredValue = Arrays.binarySearch(favoured, band[i]) > -1;
137                final Codec theCodec = favouredValue ? popCodec.getFavouredCodec() : popCodec.getUnfavouredCodec();
138                if (theCodec instanceof BHSDCodec) {
139                    final BHSDCodec bhsd = (BHSDCodec) theCodec;
140                    if (bhsd.isDelta()) {
141                        final long cardinality = bhsd.cardinality();
142                        while (band[i] > bhsd.largest()) {
143                            band[i] -= cardinality;
144                        }
145                        while (band[i] < bhsd.smallest()) {
146                            band[i] = ExactMath.add(band[i], cardinality);
147                        }
148                    }
149                }
150            }
151        }
152    }
153
154    @Override
155    public String toString() {
156        return "RunCodec[k=" + k + ";aCodec=" + aCodec + "bCodec=" + bCodec + "]";
157    }
158}