    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;
    021
    022/**
    023 * A PopulationCodec is a Codec that is well suited to encoding data that shows statistical or repetitive patterns,
    024 * containing for example a few numbers which are repeated a lot throughout the set, but not necessarily sequentially.
    025 */
    026public class PopulationCodec extends Codec {
    027
    028    private final Codec favouredCodec;
    029    private Codec tokenCodec;
    030    private final Codec unfavouredCodec;
    031    private int l;
    032    private int[] favoured;
    033
    034    public PopulationCodec(final Codec favouredCodec, final Codec tokenCodec, final Codec unvafouredCodec) {
    035        this.favouredCodec = favouredCodec;
    036        this.tokenCodec = tokenCodec;
    037        this.unfavouredCodec = unvafouredCodec;
    038    }
    039
    040    public PopulationCodec(final Codec favouredCodec, final int l, final Codec unfavouredCodec) {
    041        if (l >= 256 || l <= 0) {
    042            throw new IllegalArgumentException("L must be between 1..255");
    043        }
    044        this.favouredCodec = favouredCodec;
    045        this.l = l;
    046        this.unfavouredCodec = unfavouredCodec;
    047    }
    048
    049    @Override
    050    public int decode(final InputStream in) throws IOException, Pack200Exception {
    051        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
    052    }
    053
    054    @Override
    055    public int decode(final InputStream in, final long last) throws IOException, Pack200Exception {
    056        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
    057    }
    058
    059    @Override
    060    public int[] decodeInts(final int n, final InputStream in) throws IOException, Pack200Exception {
    061        lastBandLength = 0;
    062        favoured = new int[n]; // there must be <= n values, but probably a lot
    063        // less
    064        int result[];
    065        // read table of favorites first
    066        int smallest = Integer.MAX_VALUE, absoluteSmallest;
    067        int last = 0;
    068        int value = 0, absoluteValue;
    069        int k = -1;
    070        while (true) {
    071            value = favouredCodec.decode(in, last);
    072            if (k > -1 && (value == smallest || value == last)) {
    073                break;
    074            }
    075            favoured[++k] = value;
    076            absoluteSmallest = Math.abs(smallest);
    077            absoluteValue = Math.abs(value);
    078            if (absoluteSmallest > absoluteValue) {
    079                smallest = value;
    080            } else if (absoluteSmallest == absoluteValue) {
    081                // ensure that -X and +X -> +X
    082                smallest = absoluteSmallest;
    083            }
    084            last = value;
    085        }
    086        lastBandLength += k;
    087        // if tokenCodec needs to be derived from the T, L and K values
    088        if (tokenCodec == null) {
    089            if (k < 256) {
    090                tokenCodec = Codec.BYTE1;
    091            } else {
    092                // if k >= 256, b >= 2
    093                int b = 1;
    094                BHSDCodec codec = null;
    095                while (++b < 5) {
    096                    codec = new BHSDCodec(b, 256 - l, 0);
    097                    if (codec.encodes(k)) {
    098                        tokenCodec = codec;
    099                        break;
    100                    }
    101                }
    102                if (tokenCodec == null) {
    103                    throw new Pack200Exception("Cannot calculate token codec from " + k + " and " + l);
    104                }
    105            }
    106        }
    107        // read favorites
    108        lastBandLength += n;
    109        result = tokenCodec.decodeInts(n, in);
    110        // read unfavorites
    111        last = 0;
    112        for (int i = 0; i < n; i++) {
    113            final int index = result[i];
    114            if (index == 0) {
    115                lastBandLength++;
    116                result[i] = last = unfavouredCodec.decode(in, last);
    117            } else {
    118                result[i] = favoured[index - 1];
    119            }
    120        }
    121        return result;
    122    }
    123
    124    public int[] getFavoured() {
    125        return favoured;
    126    }
    127
    128    public Codec getFavouredCodec() {
    129        return favouredCodec;
    130    }
    131
    132    public Codec getUnfavouredCodec() {
    133        return unfavouredCodec;
    134    }
    135
    136    @Override
    137    public byte[] encode(final int value, final int last) throws Pack200Exception {
    138        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
    139    }
    140
    141    @Override
    142    public byte[] encode(final int value) throws Pack200Exception {
    143        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
    144    }
    145
    146    public byte[] encode(final int[] favoured, final int[] tokens, final int[] unfavoured) throws Pack200Exception {
    147        final int[] favoured2 = new int[favoured.length + 1];
    148        System.arraycopy(favoured, 0, favoured2, 0, favoured.length);
    149        favoured2[favoured2.length - 1] = favoured[favoured.length - 1]; // repeat last value;
    150        final byte[] favouredEncoded = favouredCodec.encode(favoured2);
    151        final byte[] tokensEncoded = tokenCodec.encode(tokens);
    152        final byte[] unfavouredEncoded = unfavouredCodec.encode(unfavoured);
    153        final byte[] band = new byte[favouredEncoded.length + tokensEncoded.length + unfavouredEncoded.length];
    154        System.arraycopy(favouredEncoded, 0, band, 0, favouredEncoded.length);
    155        System.arraycopy(tokensEncoded, 0, band, favouredEncoded.length, tokensEncoded.length);
    156        System.arraycopy(unfavouredEncoded, 0, band, favouredEncoded.length + tokensEncoded.length,
    157            unfavouredEncoded.length);
    158        return band;
    159    }
    160
    161    public Codec getTokenCodec() {
    162        return tokenCodec;
    163    }
    164}