    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.pack200;
    018
    019import java.io.IOException;
    020import java.io.InputStream;
    021import java.util.Arrays;
    022
    023import org.apache.commons.compress.utils.ExactMath;
    024
    025/**
    026 * 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
    027 * that since this codec maintains state, the instances are not reusable.
    028 */
    029public class RunCodec extends Codec {
    030
    031    private int k;
    032    private final Codec aCodec;
    033    private final Codec bCodec;
    034    private int last;
    035
    036    public RunCodec(final int k, final Codec aCodec, final Codec bCodec) throws Pack200Exception {
    037        if (k <= 0) {
    038            throw new Pack200Exception("Cannot have a RunCodec for a negative number of numbers");
    039        }
    040        if (aCodec == null || bCodec == null) {
    041            throw new Pack200Exception("Must supply both codecs for a RunCodec");
    042        }
    043        this.k = k;
    044        this.aCodec = aCodec;
    045        this.bCodec = bCodec;
    046    }
    047
    048    @Override
    049    public int decode(final InputStream in) throws IOException, Pack200Exception {
    050        return decode(in, this.last);
    051    }
    052
    053    @Override
    054    public int decode(final InputStream in, final long last) throws IOException, Pack200Exception {
    055        if (--k >= 0) {
    056            final int value = aCodec.decode(in, this.last);
    057            this.last = k == 0 ? 0 : value;
    058            return normalise(value, aCodec);
    059        }
    060        this.last = bCodec.decode(in, this.last);
    061        return normalise(this.last, bCodec);
    062    }
    063
    064    @Override
    065    public int[] decodeInts(final int n, final InputStream in) throws IOException, Pack200Exception {
    066        final int[] aValues = aCodec.decodeInts(k, in);
    067        normalise(aValues, aCodec);
    068        final int[] bValues = bCodec.decodeInts(n - k, in);
    069        normalise(bValues, bCodec);
    070        final int[] band = new int[check(n, in)];
    071        System.arraycopy(aValues, 0, band, 0, k);
    072        System.arraycopy(bValues, 0, band, k, n - k);
    073        lastBandLength = aCodec.lastBandLength + bCodec.lastBandLength;
    074        return band;
    075    }
    076
    077    @Override
    078    public byte[] encode(final int value) throws Pack200Exception {
    079        throw new Pack200Exception("Must encode entire band at once with a RunCodec");
    080    }
    081
    082    @Override
    083    public byte[] encode(final int value, final int last) throws Pack200Exception {
    084        throw new Pack200Exception("Must encode entire band at once with a RunCodec");
    085    }
    086
    087    public Codec getACodec() {
    088        return aCodec;
    089    }
    090
    091    public Codec getBCodec() {
    092        return bCodec;
    093    }
    094
    095    public int getK() {
    096        return k;
    097    }
    098
    099    private int normalise(int value, final Codec codecUsed) {
    100        if (codecUsed instanceof BHSDCodec) {
    101            final BHSDCodec bhsd = (BHSDCodec) codecUsed;
    102            if (bhsd.isDelta()) {
    103                final long cardinality = bhsd.cardinality();
    104                while (value > bhsd.largest()) {
    105                    value -= cardinality;
    106                }
    107                while (value < bhsd.smallest()) {
    108                    value = ExactMath.add(value, cardinality);
    109                }
    110            }
    111        }
    112        return value;
    113    }
    114
    115    private void normalise(final int[] band, final Codec codecUsed) {
    116        if (codecUsed instanceof BHSDCodec) {
    117            final BHSDCodec bhsd = (BHSDCodec) codecUsed;
    118            if (bhsd.isDelta()) {
    119                final long cardinality = bhsd.cardinality();
    120                for (int i = 0; i < band.length; i++) {
    121                    while (band[i] > bhsd.largest()) {
    122                        band[i] -= cardinality;
    123                    }
    124                    while (band[i] < bhsd.smallest()) {
    125                        band[i] = ExactMath.add(band[i], cardinality);
    126                    }
    127                }
    128            }
    129        } else if (codecUsed instanceof PopulationCodec) {
    130            final PopulationCodec popCodec = (PopulationCodec) codecUsed;
    131            final int[] favoured = popCodec.getFavoured().clone();
    132            Arrays.sort(favoured);
    133            for (int i = 0; i < band.length; i++) {
    134                final boolean favouredValue = Arrays.binarySearch(favoured, band[i]) > -1;
    135                final Codec theCodec = favouredValue ? popCodec.getFavouredCodec() : popCodec.getUnfavouredCodec();
    136                if (theCodec instanceof BHSDCodec) {
    137                    final BHSDCodec bhsd = (BHSDCodec) theCodec;
    138                    if (bhsd.isDelta()) {
    139                        final long cardinality = bhsd.cardinality();
    140                        while (band[i] > bhsd.largest()) {
    141                            band[i] -= cardinality;
    142                        }
    143                        while (band[i] < bhsd.smallest()) {
    144                            band[i] = ExactMath.add(band[i], cardinality);
    145                        }
    146                    }
    147                }
    148            }
    149        }
    150    }
    151
    152    @Override
    153    public String toString() {
    154        return "RunCodec[k=" + k + ";aCodec=" + aCodec + "bCodec=" + bCodec + "]";
    155    }
    156}