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