001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.harmony.pack200;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.Arrays;
024
025/**
026 * 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
027 * are repeated a lot throughout the set, but not necessarily sequentially.
028 */
029public class PopulationCodec extends Codec {
030
031    private final Codec favouredCodec;
032    private Codec tokenCodec;
033    private final Codec unfavouredCodec;
034    private int l;
035    private int[] favoured;
036
037    public PopulationCodec(final Codec favouredCodec, final Codec tokenCodec, final Codec unvafouredCodec) {
038        this.favouredCodec = favouredCodec;
039        this.tokenCodec = tokenCodec;
040        this.unfavouredCodec = unvafouredCodec;
041    }
042
043    public PopulationCodec(final Codec favouredCodec, final int l, final Codec unfavouredCodec) {
044        if (l >= 256 || l <= 0) {
045            throw new IllegalArgumentException("L must be between 1..255");
046        }
047        this.favouredCodec = favouredCodec;
048        this.l = l;
049        this.unfavouredCodec = unfavouredCodec;
050    }
051
052    @Override
053    public int decode(final InputStream in) throws IOException, Pack200Exception {
054        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
055    }
056
057    @Override
058    public int decode(final InputStream in, final long last) throws IOException, Pack200Exception {
059        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
060    }
061
062    @Override
063    public int[] decodeInts(final int n, final InputStream in) throws IOException, Pack200Exception {
064        lastBandLength = 0;
065        favoured = new int[check(n, in)]; // there must be <= n values, but probably a lot
066        // less
067        final int[] result;
068        // read table of favorites first
069        int smallest = Integer.MAX_VALUE;
070        int absoluteSmallest;
071        int last = 0;
072        int value = 0;
073        int absoluteValue;
074        int k = -1;
075        while (true) {
076            value = favouredCodec.decode(in, last);
077            if (k > -1 && (value == smallest || value == last)) {
078                break;
079            }
080            favoured[++k] = value;
081            absoluteSmallest = Math.abs(smallest);
082            absoluteValue = Math.abs(value);
083            if (absoluteSmallest > absoluteValue) {
084                smallest = value;
085            } else if (absoluteSmallest == absoluteValue) {
086                // ensure that -X and +X -> +X
087                smallest = absoluteSmallest;
088            }
089            last = value;
090        }
091        lastBandLength += k;
092        // if tokenCodec needs to be derived from the T, L and K values
093        if (tokenCodec == null) {
094            if (k < 256) {
095                tokenCodec = BYTE1;
096            } else {
097                // if k >= 256, b >= 2
098                int b = 1;
099                BHSDCodec codec;
100                while (++b < 5) {
101                    codec = new BHSDCodec(b, 256 - l, 0);
102                    if (codec.encodes(k)) {
103                        tokenCodec = codec;
104                        break;
105                    }
106                }
107                if (tokenCodec == null) {
108                    throw new Pack200Exception("Cannot calculate token codec from " + k + " and " + l);
109                }
110            }
111        }
112        // read favorites
113        lastBandLength += n;
114        result = tokenCodec.decodeInts(n, in);
115        // read unfavorites
116        last = 0;
117        for (int i = 0; i < n; i++) {
118            final int index = result[i];
119            if (index == 0) {
120                lastBandLength++;
121                result[i] = last = unfavouredCodec.decode(in, last);
122            } else {
123                result[i] = favoured[index - 1];
124            }
125        }
126        return result;
127    }
128
129    @Override
130    public byte[] encode(final int value) throws Pack200Exception {
131        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
132    }
133
134    @Override
135    public byte[] encode(final int value, final int last) throws Pack200Exception {
136        throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
137    }
138
139    public byte[] encode(final int[] favoured, final int[] tokens, final int[] unfavoured) throws Pack200Exception {
140        final int[] favoured2 = Arrays.copyOf(favoured, favoured.length + 1);
141        favoured2[favoured2.length - 1] = favoured[favoured.length - 1]; // repeat last value;
142        final byte[] favouredEncoded = favouredCodec.encode(favoured2);
143        final byte[] tokensEncoded = tokenCodec.encode(tokens);
144        final byte[] unfavouredEncoded = unfavouredCodec.encode(unfavoured);
145        final byte[] band = new byte[favouredEncoded.length + tokensEncoded.length + unfavouredEncoded.length];
146        System.arraycopy(favouredEncoded, 0, band, 0, favouredEncoded.length);
147        System.arraycopy(tokensEncoded, 0, band, favouredEncoded.length, tokensEncoded.length);
148        System.arraycopy(unfavouredEncoded, 0, band, favouredEncoded.length + tokensEncoded.length, unfavouredEncoded.length);
149        return band;
150    }
151
152    public int[] getFavoured() {
153        return favoured;
154    }
155
156    public Codec getFavouredCodec() {
157        return favouredCodec;
158    }
159
160    public Codec getTokenCodec() {
161        return tokenCodec;
162    }
163
164    public Codec getUnfavouredCodec() {
165        return unfavouredCodec;
166    }
167}