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}