View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.pack200;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.Arrays;
22  
23  /**
24   * 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
25   * are repeated a lot throughout the set, but not necessarily sequentially.
26   */
27  public class PopulationCodec extends Codec {
28  
29      private final Codec favouredCodec;
30      private Codec tokenCodec;
31      private final Codec unfavouredCodec;
32      private int l;
33      private int[] favoured;
34  
35      public PopulationCodec(final Codec favouredCodec, final Codec tokenCodec, final Codec unvafouredCodec) {
36          this.favouredCodec = favouredCodec;
37          this.tokenCodec = tokenCodec;
38          this.unfavouredCodec = unvafouredCodec;
39      }
40  
41      public PopulationCodec(final Codec favouredCodec, final int l, final Codec unfavouredCodec) {
42          if (l >= 256 || l <= 0) {
43              throw new IllegalArgumentException("L must be between 1..255");
44          }
45          this.favouredCodec = favouredCodec;
46          this.l = l;
47          this.unfavouredCodec = unfavouredCodec;
48      }
49  
50      @Override
51      public int decode(final InputStream in) throws IOException, Pack200Exception {
52          throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
53      }
54  
55      @Override
56      public int decode(final InputStream in, final long last) throws IOException, Pack200Exception {
57          throw new Pack200Exception("Population encoding does not work unless the number of elements are known");
58      }
59  
60      @Override
61      public int[] decodeInts(final int n, final InputStream in) throws IOException, Pack200Exception {
62          lastBandLength = 0;
63          favoured = new int[check(n, in)]; // there must be <= n values, but probably a lot
64          // less
65          int[] result;
66          // read table of favorites first
67          int smallest = Integer.MAX_VALUE, absoluteSmallest;
68          int last = 0;
69          int value = 0, absoluteValue;
70          int k = -1;
71          while (true) {
72              value = favouredCodec.decode(in, last);
73              if (k > -1 && (value == smallest || value == last)) {
74                  break;
75              }
76              favoured[++k] = value;
77              absoluteSmallest = Math.abs(smallest);
78              absoluteValue = Math.abs(value);
79              if (absoluteSmallest > absoluteValue) {
80                  smallest = value;
81              } else if (absoluteSmallest == absoluteValue) {
82                  // ensure that -X and +X -> +X
83                  smallest = absoluteSmallest;
84              }
85              last = value;
86          }
87          lastBandLength += k;
88          // if tokenCodec needs to be derived from the T, L and K values
89          if (tokenCodec == null) {
90              if (k < 256) {
91                  tokenCodec = BYTE1;
92              } else {
93                  // if k >= 256, b >= 2
94                  int b = 1;
95                  BHSDCodec codec;
96                  while (++b < 5) {
97                      codec = new BHSDCodec(b, 256 - l, 0);
98                      if (codec.encodes(k)) {
99                          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 }