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