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}