    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}